-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
🚧 Add support for OAuth device-code login 🚧 #5221
Conversation
a26accb
to
819df9f
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #5221 +/- ##
==========================================
- Coverage 61.48% 61.39% -0.09%
==========================================
Files 298 302 +4
Lines 20813 21037 +224
==========================================
+ Hits 12797 12916 +119
- Misses 7104 7192 +88
- Partials 912 929 +17 |
819df9f
to
44c31c3
Compare
dec275e
to
8205723
Compare
Recording with the UX/copy changes: Screen.Recording.2024-07-03.at.15.12.35.mov |
d490cf3
to
86ccab5
Compare
you're probably doing this already... but can we hide the "Authenticating with existing credentials" (and error) bit if that isn't happening? |
misclick 😓 |
Yep! That message should only show up when we're actually logging in with existing credentials, what happened in that video was misleading because in truth we already had credentials, the "first" login (with those credentials) just failed for different reasons. I'll update the recording. |
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.
cli/internal/oauth/manager/util.go
Outdated
) | ||
|
||
func NewManager(store credentials.Store) (*OAuthManager, error) { | ||
hostinfo, err := host.Info() |
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 was, erm, curious where all the extra dependencies originated from 🙈;
github.com/tklauser/go-sysconf
github.com/tklauser/go-sysconf
(number of online CPUs?)github.com/shoenig/go-m1cpu
(CPU info for m1 CPUs)github.com/power-devops/perfstat
(handling oflibperfstat
??github.com/go-ole/go-ole
(loading of Windows OLE objects)github.com/yusufpapurcu/wmi
(WQL interface to Windows Management Instrumentation (WMI))
And those all look to be related to this single host.Info()
; gopsutil/host.Info()
is doing a lot of things; https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/host/host.go#L65-L117
- Getting information about the distro-version, which;
- on Linux involves trying to parse
/etc/os-release
and more; https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/host/host_linux.go#L173-L321 - on macOS involves shelling out to
sw_vers -productVersion
https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/host/host_darwin.go#L98-L124 - and on Windows invoking the infamous tricks to try to get a version, because there is no official API for this https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/host/host_windows.go#L146-L234
- ^^ on Windows that's invoked twice because it uses PlatformVersion as KernelVersion
- on Linux involves trying to parse
- Getting the kernel architecture, which on Linux/Darwin is just
unix.Utsname
, but on Windows involves parsing processor architectures; https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/host/host_windows.go#L251-L280 - Trying to detect Virtualization, which is only implemented on Linux, but there it's a lot, including (again) parsing
/etc/os-release
; enough for the library author to implement caching, but that won't do much for a short-lived process like the CLI; https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/internal/common/common_linux.go#L168-L346 - It also tries to get
BootTime
, andUpTime
, which again do a lot, including shelling out to various binaries, and it even has special cases fordocker
; caching was implemented there as well, but won't help us here; https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/internal/common/common_linux.go#L63-L119 - It tries to obtain the number of running processes on the machine, which on Linux means listing all files in
/proc
, and for each of them trying to see if it can convert them to an Integer (in which case it's a process-id); https://github.com/shirou/gopsutil/blob/865b8c3f58d6ee4c47dab074fbf3c4b037abfd96/internal/common/common_linux.go#L41-L61- ☝️ which makes me want to sit in a corner and cry
- It tries to obtain a
HostID
(sys/kernel/random/boot_id
, on Linux, but shelling out toioreg -rd1 -c IOPlatformExpertDevice
on Darwin, and using the registry on Windows)
49ba2bf
to
ed4e811
Compare
const ( | ||
registryAuthKey = "https://index.docker.io/v1/" | ||
accessTokenKey = "access-token" | ||
refreshTokenKey = "refresh-token" | ||
) | ||
|
||
func (m *OAuthManager) fetchTokensFromStore() (access, refresh string, err error) { | ||
accessAuth, err := m.credStore.Get(registryAuthKey + accessTokenKey) | ||
if err != nil { | ||
return access, refresh, fmt.Errorf("failed to fetch access token: %w", err) | ||
} | ||
access = accessAuth.Password | ||
|
||
refreshAuth, err := m.credStore.Get(registryAuthKey + refreshTokenKey) | ||
if err != nil { | ||
return access, refresh, fmt.Errorf("failed to fetch refresh token: %w", err) | ||
} | ||
refresh = refreshAuth.Password | ||
|
||
return | ||
} |
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 may need some other way to construct the access and refresh token keys (prepend as a subdomain?), or make sure that there's no code that takes the registry hostname into account - like #3599 + #5195.
Otherwise, the access/refresh token could end up being used as an actual auth token.
I haven't investigated it in detail, so it might not be an actual issue in this case, but thought it's worth bringing up 😅
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.
Hmm, that code only does that for non-official registries, so it should be fine (and indeed, that works). We can make sure it continues to work by adding some e2e tests, but maybe we can think of some other way to store these.
I still also want to try to implement some of these at the credstore level, so I'll see how that looks too/if this still matters.
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.
Yeah while it might not be a problem for the CLI code itself, I'm curious if other credential helpers might also do something like this.
Implementing these extra tokens on the credential store level would be nice indeed.
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 can check! What I was going to do was still at the CLI level, but as a wrapper to the CLI "credential store" implementations.
Signed-off-by: Laura Brehm <[email protected]>
ed4e811
to
4029dbc
Compare
- What I did
WIP PR for discussion that adds support for the device-code flow OAuth login, when authenticating against the official registry.
I'll follow up with another PR to explore implementing this at the credential store layer/using a new credential store.
- How I did it
Added
cli/internal/oauth
, which includes a newOAuthManager
that is capable of fulfilling a device-code flow against the official registry.Fetching credentials remains the same, as the returned access token can be transparently used in place of the password when authenticating.
Login and logout flows now defer to
OAuthManager
when running against the official registry.- How to verify it
Run
docker login
, and check your credentials store to find the stored access tokens. Attempt to do an operation which requires authentication (such as pushing an image to your repo) and verify it works.- Description for the changelog
- A picture of a cute animal (not mandatory but encouraged)