Closed gcapizzi closed 3 years ago
I have taken a quick look at the CLI codebase, and in particular at the cf api
command.
The command delegates to Actor.SetTarget
, which, in order:
Config
gets loaded in the CommandParser
and then passed to each command via the Setup
method. It gets then used to instantiate the wrapped clients, so it is available when the wrappers are wired in. This means that we could instantiate a different ConnectionWrapper
based on the Config
:
ApiCommand
tells the Actor
to fetch information about the API and to store them into the Config
;BaseCommand
) receives the Config
and injects it into its clients, which in turn get injected into the Actor
;GetNewClientsAndConnectToCF
can then use the Config
to decide which ConnectionWrappers
to apply!Today I systematically searched for all the code paths that used a UAAClient
. Here is the list of Actor
methods that do, alongside the commands they get invoked from:
Authenticate(credentials map[string]string, origin string, grantType uaa.GrantType) error
auth
login
CreateOrgRole(roleType constant.RoleType, orgGUID string, userNameOrGUID string, userOrigin string, isClient bool (v7action.Warnings, error)
set-org-role
create-space
set-space-role
CreateSpaceRole(roleType constant.RoleType, orgGUID string, spaceGUID string, userNameOrGUID string, userOrigin string, isClient bool) (v7action.Warnings, error)
create-space
set-space-role
CreateUser(username string, password string, origin string) (resources.User, v7action.Warnings, error)
create-user
DeleteUser(userGuid string) (v7action.Warnings, error)
delete-user
GetLoginPrompts() (map[string]coreconfig.AuthPrompt, error)
login
GetSSHPasscode() (string, error)
ssh-code
GetSecureShellConfigurationByApplicationNameSpaceProcessTypeAndIndex(appName string, spaceGUID string, processType string, processIndex uint) (v7action.SSHAuthentication, v7action.Warn ings, error)
ssh
GetUAAAPIVersion() (string, error)
auth
GetUser(username, origin string) (resources.User, error)
create-user
delete-user
RefreshAccessToken() (string, error)
logs
oauth-token
RevokeAccessAndRefreshTokens() error
logout
ScheduleTokenRefresh(func(time.Duration) <-chan time.Time, chan struct{}, chan struct{}) (<-chan error, error)
logs
UpdateUserPassword(userGUID string, oldPassword string, newPassword string) error
passwd
Some observations:
interface
that we can implement differently for cf-k8s
. Authenticate
, GetLoginPrompts
, GetUAAAPIVersion
, RevokeAccessAndRefreshTokens
, UpdateUserPassword
are all used by the auth flow commands (login
, auth
, logout
, passwd
).CreateUser
, DeleteUser
and GetUser
are used in the user management commands (create-user
, delete-user
).GetSSHPasscode
and GetSecureShellConfigurationByApplicationNameSpaceProcessTypeAndIndex
are used in the ssh*
commands.RefreshAccessToken
and ScheduleTokenRefresh
) are needed by log
to keep the access token fresh while streaming logs.
ConnectionWrapper
, as the logs are streamed from Log Cache via a LogCacheClient
, which is wired to use the Config.AccessToken
method as its token-generating function.RefreshAccessToken
mutates the Config
which then returns the new token to LogCacheClient
: Config
is effectively used as shared mutable state.client-go
make it trivial to generate an authenticated http.Client
which can then be injected in LogCacheClient
.CreateOrgRole
and CreateSpaceRole
are interesting: we definitely want to implement these, but I'm still not sure how.@emalm had an interesting question on our authentication doc: can we leverage cf login
to allow users to choose one of the AuthInfos
stored in $KUBECONFIG
vs trying to detect it automatically? The answer is yes!
This is what cf login
does:
LoginCommand
calls Actor.GetLoginPrompts
to get a list of prompts to submit to the user via the UI
.AuthPrompt
]() has a Type
, currently either TEXT
or PASSWORD
. Different UI
methods are called based on this.Actor.Authenticate
.Here is how we could modify it:
GetLoginPrompts
and Authenticate
, we could prompt the user for the AuthInfo
they want to use, and store it in the Config
.AuthInfo
s tend to have long and hard-to-type names, we could introduce a new AuthPrompt.Type
called MENU
and use UI.DisplayTextMenu
for it.AuthPrompt.DisplayName
(e.g. separated by commas) or in a new Entries
field.This flow looks more intuitive and more similar to what CF users already do. It also does not need the Kubernetes cluster URL, which simplifies the shim implementation, as the root endpoint could just return a "kubernetes": true
flag.
Methods trying to parse the JWT token:
Actor
RefreshAccessToken() (string, error)
ScheduleTokenRefresh(func(time.Duration) <-chan time.Time, chan struct{}, chan struct{}) (<-chan error, error)
ParseAccessToken(string) (jwt.JWT, error)
UAAAuthentication.Make(*cloudcontroller.Request, *cloudcontroller.Response) error
Config
CurrentUser() (User, error)
CurrentUserName() (string, error)
The Actor
methods are only invoked in cf logs
and cf oauth-token
, which we can ignore for now. We're going to provide an alternative ConnectionWrapper
implementation to UAAAuthentication
, so we don't care about that either.
Unfortunately the Config
methods are called literally everywhere. I have identified three reasons:
CreateSpaceCommand
, the CLI assigns the roles of Space Manager and Space Developer to the user creating the space.The username from $KUBECONFIG
is usually not a user name, but a cluster name. It could be fine for 1 and 2, but not for 3. I can think of two strategies to handle this:
CurrentUser
to return the real username, using the techniques described here to introduce a /whoami
endpoint.CreateSpaceRole
which ignores the userNameOrGUID
argument if it's equal to the current output of CurrentUser
. The shim could then default the username to the one in the token if it's missing.Today I'm looking at a couple of other interfaces that we might have to (partially) reimplement:
command.SharedActor
has an IsLoggedIn
method. The current implementation checks if tokens are present in the config. We could replace it with checking if Kubernetes user is present in the config.command.Config
has quite a few auth-related (and UAA-related methods), including the CurrentUser*
methods mentioned in my previous comment. The implementation of this interface gets wired in very early in the program lifecycle, much before the code to recognise a cf-k8s
foundation could run. We might have a way to replace/decorate it later on, I'm on it. We started hacking the cli and try to integrate it with cfshim and we scoredcertain progress.
Setting up the dev environment:
wip-auth
brach of cf-crd-explorationswip-k8s
branch of our cli forkalice@vcap.me
and put it in the kubeconfig, see this README for details. No need to login with github, just use the email login on the dex pagecli
forkBehold!
❯ ./cli api http://localhost:9000
Setting API endpoint to http://localhost:9000...
Warning: Insecure http API endpoint detected: secure https API endpoints are recommended
OK
API endpoint: http://localhost:9000
API version:
Not logged in. Use 'cli login' or 'cli login --sso' to log in.
❯ ./cli login
API endpoint: http://localhost:9000
Warning: Insecure http API endpoint detected: secure https API endpoints are recommended
Warning: unable to determine whether targeted API's version meets minimum supported.
1. alice
2. gke_cff-eirini-peace-pods_europe-west1-b_cf4k8s4a8e
3. gke_cff-eirini-peace-pods_europe-west1-b_dex-pinniped
4. gke_cff-eirini-peace-pods_europe-west1-b_pinniped
5. kind-cross-org-1
6. kind-cross-org-2
7. kind-dex-play
Choose your Kubernetes user (enter to skip): 1
Authenticating...
OK
Targeted org foo.
Targeted space bar.
API endpoint: http://localhost:9000
API version:
user: alice
org: foo
space: bar
The cli would show a menu with the entries from kubeconfig, note that alice
is there! After being chosen, the UI reports that you are logged in as alice
❯ ./cli apps
Getting apps in org foo / space bar as alice...
Unexpected Response
Response Code: 500
Code: 0, Title: , Detail: {"errors":[{"detail":"error fetching app: apps.apps.cloudfoundry.org is forbidden: User \"oidc:alice@vcap.me\" cannot list resource \"apps\" in API group \"apps.cloudfoundry.org\" at the cluster scope","title":"ServerError","code":10001}]}
FAILED
Good, we are hitting the shim with alice's JWT token so K8S knows who we are. alice
is not allowed to do that though... yet
❯ k apply -f ~/workspace/auth-explore/run-cfshim/alice-viewer-rolebinding.yml
clusterrolebinding.rbac.authorization.k8s.io/alice-viewer created
clusterrole.rbac.authorization.k8s.io/app-viewer created
clusterrolebinding.rbac.authorization.k8s.io/alice-apps-viewer created
❯ ./cli apps
Getting apps in org foo / space bar as alice...
No apps found
Let's create an app and try to list it:
❯ k apply -f ~/workspace/cf-crd-explorations/config/samples/cf-crds/app.yaml
app.apps.cloudfoundry.org/my-app-guid created
~/workspace/cli wip-k8s
❯ ./cli apps
Getting apps in org foo / space bar as alice...
Error unmarshalling the following into a cloud controller error: 404 page not found
FAILED
This error is caused by the shim not implementing the /processes
endpoint. However, this is irrelevant to the spike
Blocking until the rest of the team can take a look at the draft proposal here
@mnitchev, could you please grant global comment-level access to that proposal doc? Thanks!
@emalm - global comment-level enabled.
The bulk of our authentication support work will happen in the
cf
CLI. The idea is to change the CLI so that, when pointed at acf-k8s
foundation, it leverages the user's$KUBECONFIG
and possibly code fromclient-go
to authenticate exactly in the same way askubectl
would.In #62 we have proved that we can support
$KUBECONFIG
and reuse a lot ofclient-go
functionality, while implementing the same interface the CLI already uses for UAA-based authentication.Now we need to get a more thorough understanding of all changes needed to implement our strategy. Some questions that come to mind:
cf-k8s
and instantiate ourConnectionWrapper
implementation? Can those two things even happen in the same place with the current architecture?ConnectionWrapper
enough to abstract all authentication aspects?The result might look a bit like a Kubernetes Enhancement Proposal although probably not so detailed!
Deliverable
Let's start this on a Google Doc, and then eventually open a GItHub issue on the CLI repository once we reach agreement in the team.