-
Notifications
You must be signed in to change notification settings - Fork 40
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
OIDC Authentication, and Cloud CLI Contexts #1082
base: master
Are you sure you want to change the base?
Conversation
@nickmisasi: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. I understand the commands that are listed here |
…llow list for said clients
"strings" | ||
"time" | ||
|
||
"golang.org/x/oauth2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Borrowed the OAuth2 built in types for some marshaling, but couldn't use it for token exchange here as MS requires a custom scope to be passed, and this library doesn't support doing that when using refresh tokens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of comments, but I'll re-review this.
newToken, err := Refresh(ctx, config, auth.RefreshToken) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
auth.AccessToken = newToken.AccessToken |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- At every refresh, we also get a new refresh token. While the previous one is still valid (that's a weird MS choice for a public OAuth2 client), we should stop using it and store the new refresh token.
Docs: https://learn.microsoft.com/en-us/entra/identity-platform/refresh-tokens#token-lifetime
- We also need to update the
auth.ExpiresAt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had left this intentionally as a "feature" to enforce re-authentications more frequently, but I guess we can defer that functionality to the auth side. I've changed it to match your suggestion but if you'd like me to change it back let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean. Can we actually revert the change with the refresh token? Apologies for that!
Reasoning:
- For public clients that are not single-page applications (SPAs), such as those utilizing the device authorization grant (device flow), the default refresh token lifetime is 90 days. This is already a lot for a public client but it's non-configurable.
- Each time a refresh token is used to acquire a new access token, Microsoft Entra ID issues a new refresh token, that inherits the same default lifetime as the original. As long as the refresh token is used within its valid period, it is continuously renewed, potentially allowing indefinite access without requiring the user to re-authenticate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted back to not using the new RefreshToken
command.Flags().StringSliceVar(&flags.RestrictedClientIDs, "restricted-client-ids", []string{}, "The list of restricted client IDs.") | ||
command.Flags().StringSliceVar(&flags.RestrictedClientAllowedEndpointsList, "restricted-client-allowed-endpoints", []string{}, "The list of restricted endpoints.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't we currently having a fixed list of restricted client IDs and restricted endpoints that we can have as a default config?
How is this command usually run? I'd like to avoid the human error of someone missing adding all the needed client IDs and restricted endpoints here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is run through IaC YAML manifests anywhere that the authentication would matter - see here we currently set allow-list-cidr-ranges and vpn-cidr ranges similarly to the way these would be set here, so I expect we should be ok from the human error side
Getting rate limited on a build step. Will try again later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 last comments, looks great otherwise!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heya, have some initial feedback from my testing.
cmd/cloud/clicontext/clicontext.go
Outdated
var contextsData Contexts | ||
if _, statErr := os.Stat(contextFilePath); errors.Is(statErr, os.ErrNotExist) { | ||
contextsData = bootstrapFirstContext() | ||
WriteContexts(&contextsData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are ignoring the return error here and this seems to be hiding an issue where bootstrapping will fail on a completely new bootstrap. I believe the issue is the .cloud
directory isn't being created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! This directory is created when the provisioner is run so I never ran into it. Fixed
cmd/cloud/clicontext/clicontext.go
Outdated
homeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
contextFilePath := filepath.Join(homeDir, ".cloud", "contexts.json") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's take this logic and put it into a reusable helper function to rule out any typos on the default context file path.
Summary
This PR introduces 2 major features
OIDC Authentication.
Defaulted to off, unless the server is provided with a Client ID, Secret, and Organization URL.
Setting the aforementioned parameters will result in the provisioner validating requests to /api/* contain a valid Access Token from Azure AD.
Authentication for CLI's is handled using Device Authorization grants, via
cloud login
command. Authentication data (access tokens, refresh tokens, etc) are stored in a contextScreenshots:
Login page of Azure AD (Browser auto-opens)
Logs in terminal when initiated:
CLI Contexts
Most users have their own custom hacks or bash scripts to automate the passing of
--server
to handle interacting with different environments. This PR introduces the concept of contexts, stored in~/.cloud/contexts.json
Contexts store:
A new CLI option now exists
cloud contexts <command>
with the following options:Additionally, a
cloud login
command has been added, which allows you to retrigger the login flow for a particular context, should you run into authentication issues.The
--server
parameter has been removed. Instead, usecloud context create
to create a context for each Provisioner environment you interact with. To switch between them, usecloud context set-current
. Allcloud
commands will initiate based off of the currently set context (todo: support--context
to switch contexts for a single command)On first run of the Cloud CLI with this change, the contexts.json file will be created for you, containing one context with alias
local
pointing athttp://localhost:8075
.When creating a new context, if you provide a Client ID and OrgURL, the CLI will automatically initiate the login flow for you. To skip the authentication flow, pass the argument
--skip-auth
Ticket Link
https://mattermost.atlassian.net/browse/CLD-8406
Release Note