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

@@ -19,11 +19,15 @@ package restapi
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/minio-go/v6/pkg/credentials"
)
// RoundTripFunc .
@@ -41,8 +45,17 @@ func NewTestClient(fn RoundTripFunc) *http.Client {
}
}
func Test_serverMkube(t *testing.T) {
var audience = ""
var creds = &credentials.Value{
AccessKeyID: "fakeAccessKeyID",
SecretAccessKey: "fakeSecretAccessKey",
SessionToken: "fakeSessionToken",
SignerType: 0,
}
func Test_serverMkube(t *testing.T) {
jwt, _ := auth.NewJWTWithClaimsForClient(creds, []string{""}, audience)
dummyBody := ioutil.NopCloser(bytes.NewReader([]byte("foo")))
OKclient := NewTestClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
@@ -83,16 +96,53 @@ func Test_serverMkube(t *testing.T) {
args: args{
client: OKclient,
recorder: httptest.NewRecorder(),
req: &http.Request{URL: testURL},
req: &http.Request{
Body: dummyBody,
URL: testURL,
Header: http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", jwt)},
},
},
},
wantCode: 200,
},
{
name: "Unsuccessful request - wrong jwt credentials",
args: args{
client: OKclient,
recorder: httptest.NewRecorder(),
req: &http.Request{
Body: dummyBody,
URL: testURL,
Header: http.Header{
"Authorization": []string{"EAEAEAEAE"},
},
},
},
wantCode: 500,
},
{
name: "Unsuccessful request - no mkube service account token provided",
args: args{
client: OKclient,
recorder: httptest.NewRecorder(),
req: &http.Request{
Body: dummyBody,
URL: testURL,
Header: http.Header{},
},
},
wantCode: 500,
},
{
name: "Unsuccessful request",
args: args{
client: badClient,
recorder: httptest.NewRecorder(),
req: &http.Request{URL: testURL},
req: &http.Request{
URL: testURL,
Body: dummyBody,
},
},
wantCode: 500,
},
@@ -101,7 +151,10 @@ func Test_serverMkube(t *testing.T) {
args: args{
client: refusedClient,
recorder: httptest.NewRecorder(),
req: &http.Request{URL: testURL},
req: &http.Request{
URL: testURL,
Body: dummyBody,
},
},
wantCode: 500,
},
@@ -111,7 +164,7 @@ func Test_serverMkube(t *testing.T) {
serverMkube(tt.args.client, tt.args.recorder, tt.args.req)
resp := tt.args.recorder.Result()
if resp.StatusCode != tt.wantCode {
t.Errorf("Invalid code returned")
t.Errorf("Invalid code returned, expected: %d received: %d", tt.wantCode, resp.StatusCode)
return
}
})