-
Notifications
You must be signed in to change notification settings - Fork 55
[CMP] Add poptoken creation and validation to moc #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
wecha-ms-v2
wants to merge
7
commits into
feature/cmp
Choose a base branch
from
user/wecha/pop2
base: feature/cmp
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b73f618
add poptoken support
wecha-ms a5937a7
add objectpath customclaim, add nonce check, simplify rsakeymgr, adde…
wecha-ms 6f12a53
temp
wecha-ms 2dc2fd1
merged poptoken and rsakeymgr logic into poptokenscheme
wecha-ms 9621437
minor cleanup
wecha-ms 2189aff
cleanup2
wecha-ms 6192ae2
add kid validation
wecha-ms File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package poptoken | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/rsa" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/lestrrat-go/jwx/jwk" | ||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| const ( | ||
| IssuerPostfix = "common/discovery/keys" | ||
| RefreshJwkInterval = time.Hour * 24 | ||
| ) | ||
|
|
||
| // Wrapper around jwk library to retrieve and refresh the jwk endpoints from Entra/AAD | ||
| type jwkManager struct { | ||
| // STS JWK endpoint, e.g. "https://login.microsoftonline.com/common/discovery/keys" | ||
| jwkEndpoint string | ||
| ar *jwk.AutoRefresh | ||
| } | ||
|
|
||
| type JwkInterface interface { | ||
| GetPublicKey(kid string) (*rsa.PublicKey, error) | ||
| } | ||
|
|
||
| func (j *jwkManager) GetPublicKey(kid string) (*rsa.PublicKey, error) { | ||
| ctx := context.Background() | ||
| keys, err := j.ar.Fetch(ctx, j.jwkEndpoint) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to look up jwk endpoint %s to retrieve keys", j.jwkEndpoint) | ||
| } | ||
|
|
||
| key, ok := keys.LookupKeyID(kid) | ||
| if !ok { | ||
| return nil, fmt.Errorf("failed to find kid %s in jwk endpoint %s", kid, j.jwkEndpoint) | ||
| } | ||
|
|
||
| var pKey rsa.PublicKey | ||
| if err := key.Raw(&pKey); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &pKey, nil | ||
| } | ||
|
|
||
| func NewJwkManager(authorityUrl string, refreshInterval time.Duration) (*jwkManager, error) { | ||
| jwkEndpoint := appendUrl(authorityUrl, IssuerPostfix) | ||
| ctx, _ := context.WithCancel(context.Background()) | ||
| ar := jwk.NewAutoRefresh(ctx) | ||
| ar.Configure(jwkEndpoint, jwk.WithMinRefreshInterval(refreshInterval)) | ||
| _, err := ar.Refresh(ctx, jwkEndpoint) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to refresh the jwk endpoint %s", jwkEndpoint) | ||
| } | ||
|
|
||
| return &jwkManager{jwkEndpoint: jwkEndpoint, ar: ar}, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package poptoken | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
|
|
||
| "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" | ||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| // Msal client to generate the pop token. Note that the msal sdk does not provide pop token support | ||
| // out of the box, refer to NodeAgentPopTokenScheme. | ||
| type MsalAuthProvider struct { | ||
| clientId string | ||
| tenantId string | ||
| authorityUrl string | ||
| scope []string | ||
| clientCertPath string | ||
| } | ||
|
|
||
| func (m MsalAuthProvider) refreshConfidentialClient() (*confidential.Client, error) { | ||
| pemData, err := os.ReadFile(m.clientCertPath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| cert, privateKey, err := confidential.CertFromPEM(pemData, "") | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // the PEM file can contain multiple intermediate certificates to validate TLS cert chaining but we are only interested in our | ||
| // own certificate which is always the first one. See https://www.rfc-editor.org/rfc/rfc5246#section-7.4.2 | ||
| cert = cert[:1] | ||
| cred, err := confidential.NewCredFromCert(cert, privateKey) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to create confidential credential from certificate") | ||
| } | ||
|
|
||
| // use Subject NAame and Issuer (SN+I) authentication to request for token. For this to work, Withx5c() must be set | ||
| // to pass the certificate chain in the request header. | ||
| confidentialClient, err := confidential.New(m.authorityUrl, m.clientId, cred, confidential.WithX5C()) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to create confidential client") | ||
| } | ||
|
|
||
| return &confidentialClient, nil | ||
| } | ||
|
|
||
| func (m MsalAuthProvider) GetToken(targetResourceId string, grpcObjectPath string) (string, error) { | ||
|
|
||
| // TODO: the underlying client certificate will be refreshed, hence we need to also pick up the new certificate | ||
| // Longer run we can cache the client but for now we will refresh the client for every token call. | ||
| confidentialClient, err := m.refreshConfidentialClient() | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| popTokenScheme, err := NewNodeAgentPopTokenAuthScheme(targetResourceId, grpcObjectPath) | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to create new pop token scheme") | ||
| } | ||
|
|
||
| result, err := confidentialClient.AcquireTokenByCredential(context.Background(), m.scope, confidential.WithAuthenticationScheme(popTokenScheme)) | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to get token") | ||
| } | ||
| return result.AccessToken, nil | ||
| } | ||
|
|
||
| func NewMsalClient(clientId string, tenantId, authorityUrl string, clientCertPath string) (*MsalAuthProvider, error) { | ||
| m := &MsalAuthProvider{ | ||
| clientId: clientId, | ||
| tenantId: tenantId, | ||
| authorityUrl: appendUrl(authorityUrl, tenantId), | ||
| clientCertPath: clientCertPath, | ||
| scope: []string{appendUrl(clientId, ".default")}, // intentionally target itself as the pop token custom claim will contain the actual audience. | ||
| } | ||
|
|
||
| // sanity check to ensure client is setup correctly | ||
| _, err := m.refreshConfidentialClient() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return m, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package poptoken | ||
|
|
||
| import "github.com/google/uuid" | ||
|
|
||
| // Implements the interface for MSAL SDK to callback when creating the poptoken. | ||
| // See AuthenticationScheme interface in https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/main/apps/internal/oauth/ops/authority/authority.go#L146 | ||
| type NodeAgentPopTokenAuthScheme struct { | ||
wecha-ms-v2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| *PopTokenAuthScheme | ||
| } | ||
|
|
||
| // Create a new instance of NodeAgentPopTokenAuthScheme. | ||
| // targetResourceId: the ARM resourceId representing the edge node machine. This is the Arc For Server resource Id and is part of the node entity. | ||
| // grpcObjectId: the uri to the grpc entity, e.g. container. This will be passed in as part of the grpc metadata. | ||
| func NewNodeAgentPopTokenAuthScheme(targetNodeId string, grpcObjectId string) (*NodeAgentPopTokenAuthScheme, error) { | ||
| popTokenScheme, err := NewPopTokenAuthScheme( | ||
| map[string]interface{}{ | ||
| "nodeid": targetNodeId, | ||
| "p": grpcObjectId, | ||
| "nonce": uuid.New().String(), | ||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &NodeAgentPopTokenAuthScheme{ | ||
| PopTokenAuthScheme: popTokenScheme, | ||
| }, nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.