Files
object-browser/restapi/mkube_test.go
Lenin Alevski 1e7f272a67 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
````
2020-06-23 11:37:46 -07:00

173 lines
4.3 KiB
Go

// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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 .
type RoundTripFunc func(req *http.Request) (*http.Response, error)
// RoundTrip .
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
//NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: fn,
}
}
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,
Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)),
Header: make(http.Header),
}, nil
})
badClient := NewTestClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 500,
Body: ioutil.NopCloser(bytes.NewBufferString(`NOTOK`)),
Header: make(http.Header),
}, errors.New("something wrong")
})
refusedClient := NewTestClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 500,
Body: ioutil.NopCloser(bytes.NewBufferString(`NOTOK`)),
Header: make(http.Header),
}, errors.New("connection refused")
})
testURL, _ := url.Parse("/api/v1/clusters")
type args struct {
client *http.Client
recorder *httptest.ResponseRecorder
req *http.Request
}
tests := []struct {
name string
args args
wantCode int
}{
{
name: "Successful request",
args: args{
client: OKclient,
recorder: httptest.NewRecorder(),
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,
Body: dummyBody,
},
},
wantCode: 500,
},
{
name: "refused request",
args: args{
client: refusedClient,
recorder: httptest.NewRecorder(),
req: &http.Request{
URL: testURL,
Body: dummyBody,
},
},
wantCode: 500,
},
}
for _, tt := range tests {
t.Run(tt.name, func(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, expected: %d received: %d", tt.wantCode, resp.StatusCode)
return
}
})
}
}