MCS service account authentication with Mkube (#166)

`MCS` will authenticate against `Mkube`using bearer tokens via HTTP
`Authorization` header. The user will provide this token once
in the login form, MCS will validate it against Mkube (list tenants) and
if valid will generate and return a new MCS sessions
with encrypted claims (the user Service account token will be inside the
JWT in the data field)

Kubernetes

The provided `JWT token` corresponds to the `Kubernetes service account`
that `Mkube` will use to run tasks on behalf of the
user, ie: list, create, edit, delete tenants, storage class, etc.

Development

If you are running mcs in your local environment and wish to make
request to `Mkube` you can set `MCS_M3_HOSTNAME`, if
the environment variable is not present by default `MCS` will use
`"http://m3:8787"`, additionally you will need to set the
`MCS_MKUBE_ADMIN_ONLY=on` variable to make MCS display the Mkube UI

Extract the Service account token and use it with MCS

For local development you can use the jwt associated to the `m3-sa`
service account, you can get the token running
the following command in your terminal:

```
kubectl get secret $(kubectl get serviceaccount m3-sa -o
jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64
--decode
```

Then run the mcs server

```
MCS_M3_HOSTNAME=http://localhost:8787 MCS_MKUBE_ADMIN_ONLY=on ./mcs
server
```

Self-signed certificates and Custom certificate authority for Mkube

If Mkube uses TLS with a self-signed certificate, or a certificate
issued by a custom certificate authority you can add those
certificates usinng the `MCS_M3_SERVER_TLS_CA_CERTIFICATE` env variable

````
MCS_M3_SERVER_TLS_CA_CERTIFICATE=cert1.pem,cert2.pem,cert3.pem ./mcs
server
````
This commit is contained in:
Lenin Alevski
2020-06-23 11:37:46 -07:00
committed by GitHub
parent 1aec2d879e
commit 1e7f272a67
36 changed files with 1532 additions and 387 deletions

View File

@@ -61,18 +61,25 @@ func registerLoginHandlers(api *operations.McsAPI) {
}
return user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse)
})
api.UserAPILoginMkubeHandler = user_api.LoginMkubeHandlerFunc(func(params user_api.LoginMkubeParams) middleware.Responder {
loginResponse, err := getLoginMkubeResponse(params.Body)
if err != nil {
return user_api.NewLoginMkubeDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
}
return user_api.NewLoginMkubeCreated().WithPayload(loginResponse)
})
}
// login performs a check of minioCredentials against MinIO, generates some claims and returns the jwt
// login performs a check of mcsCredentials against MinIO, generates some claims and returns the jwt
// for subsequent authentication
func login(credentials MCSCredentials, actions []string) (*string, error) {
// try to obtain minioCredentials,
// try to obtain mcsCredentials,
tokens, err := credentials.Get()
if err != nil {
log.Println("error authenticating user", err)
return nil, errInvalidCredentials
}
// if we made it here, the minioCredentials work, generate a jwt with claims
// if we made it here, the mcsCredentials work, generate a jwt with claims
jwt, err := auth.NewJWTWithClaimsForClient(&tokens, actions, getMinIOServer())
if err != nil {
log.Println("error authenticating user", err)
@@ -115,7 +122,7 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
log.Println("error login:", err)
return nil, errInvalidCredentials
}
credentials := mcsCredentials{minioCredentials: creds}
credentials := mcsCredentials{mcsCredentials: creds}
// obtain the current policy assigned to this user
// necessary for generating the list of allowed endpoints
userInfo, err := adminClient.getUserInfo(ctx, *lr.AccessKey)
@@ -127,7 +134,7 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
// by default every user starts with an empty array of available actions
// therefore we would have access only to pages that doesn't require any privilege
// ie: service-account page
actions := []string{}
var actions []string
// if a policy is assigned to this user we parse the actions from there
if policy != nil {
actions = acl.GetActionsStringFromPolicy(policy)
@@ -148,7 +155,9 @@ func getLoginDetailsResponse() (*models.LoginDetails, error) {
ctx := context.Background()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
if oauth2.IsIdpEnabled() {
if acl.GetOperatorOnly() {
loginStrategy = models.LoginDetailsLoginStrategyServiceAccount
} else if oauth2.IsIdpEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(ctx, nil)
@@ -238,7 +247,7 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
log.Println("error login:", err)
return nil, errorGeneric
}
credentials := mcsCredentials{minioCredentials: creds}
credentials := mcsCredentials{mcsCredentials: creds}
jwt, err := login(credentials, actions)
if err != nil {
return nil, err
@@ -251,3 +260,23 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
}
return nil, errorGeneric
}
// getLoginMkubeResponse validate the provided service account token against mkube
func getLoginMkubeResponse(lmr *models.LoginMkubeRequest) (*models.LoginResponse, error) {
creds, err := newMcsCredentials("", *lmr.Jwt, "")
if err != nil {
log.Println("error login:", err)
return nil, errInvalidCredentials
}
credentials := mcsCredentials{mcsCredentials: creds}
var actions []string
jwt, err := login(credentials, actions)
if err != nil {
return nil, err
}
// serialize output
loginResponse := &models.LoginResponse{
SessionID: *jwt,
}
return loginResponse, nil
}