Skip to content

Commit 65ec350

Browse files
committed
MCS service account authentication with Mkube
`MCS` will authenticate against `Mkube`using bearer tokens via HTTP `Authorization` header. Kubernetes By default, if you don't provide `MCS_K8S_SA_JWT` mcs will assume is running inside a `kubernetes` pod and will try to read the jwt from `/var/run/secrets/kubernetes.io/serviceaccount/token` and use it. The provided `JWT token` corresponds to the `Kubernetes service account` that `Mkube` will use to run tasks on behalf of `MCS` 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"` 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_K8S_SA_JWT=eyJh... ./mcs server ```
1 parent 34ff3d7 commit 65ec350

File tree

13 files changed

+245
-94
lines changed

13 files changed

+245
-94
lines changed

docs/mcs_service_account_mkube.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# MCS service account authentication with Mkube
2+
3+
`MCS` will authenticate against `Mkube`using bearer tokens via HTTP `Authorization` header.
4+
5+
# Kubernetes
6+
7+
By default, if you don't provide `MCS_K8S_SA_JWT` mcs will assume is running inside a `kubernetes` pod and will try to read
8+
the jwt from `/var/run/secrets/kubernetes.io/serviceaccount/token` and use it.
9+
10+
The provided `JWT token` corresponds to the `Kubernetes service account` that `Mkube` will use to run tasks on behalf of `MCS`
11+
ie: list, create, edit, delete tenants, storage class, etc.
12+
13+
# Development
14+
15+
If you are running mcs in your local environment and wish to make request to `Mkube` you can set `MCS_M3_HOSTNAME`, if
16+
the environment variable is not present by default `MCS` will use `"http://m3:8787"`
17+
18+
## Extract the Service account token and use it with MCS
19+
20+
For local development you can use the jwt associated to the `m3-sa` service account, you can get the token running
21+
the following command in your terminal:
22+
23+
```
24+
kubectl get secret $(kubectl get serviceaccount m3-sa -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decode
25+
```
26+
27+
Then run the mcs server
28+
29+
```
30+
MCS_M3_HOSTNAME=http://localhost:8787 MCS_K8S_SA_JWT=eyJh... ./mcs server
31+
```

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ require (
1818
github.com/json-iterator/go v1.1.9
1919
github.com/minio/cli v1.22.0
2020
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6
21-
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb
22-
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f
21+
github.com/minio/minio v0.0.0-20200603201854-5686a7e27319
22+
github.com/minio/minio-go/v6 v6.0.56
2323
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
2424
github.com/satori/go.uuid v1.2.0
2525
github.com/stretchr/testify v1.5.1

go.sum

Lines changed: 45 additions & 5 deletions
Large diffs are not rendered by default.

pkg/auth/jwt.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import (
2626
"fmt"
2727
"io"
2828
"log"
29+
"net/http"
2930
"strings"
3031

3132
jwtgo "github.com/dgrijalva/jwt-go"
33+
"github.com/go-openapi/swag"
3234
xjwt "github.com/minio/mcs/pkg/auth/jwt"
3335
"github.com/minio/minio-go/v6/pkg/credentials"
3436
"github.com/minio/minio/cmd"
@@ -182,3 +184,42 @@ func decrypt(data []byte) ([]byte, error) {
182184
}
183185
return plaintext, nil
184186
}
187+
188+
// GetTokenFromRequest returns a token from a http Request
189+
// either defined on a cookie `token` or on Authorization header.
190+
//
191+
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
192+
func GetTokenFromRequest(r *http.Request) (*string, error) {
193+
// Get Auth token
194+
var reqToken string
195+
196+
// Token might come either as a Cookie or as a Header
197+
// if not set in cookie, check if it is set on Header.
198+
tokenCookie, err := r.Cookie("token")
199+
if err != nil {
200+
headerToken := r.Header.Get("Authorization")
201+
// reqToken should come as "Bearer <token>"
202+
splitHeaderToken := strings.Split(headerToken, "Bearer")
203+
if len(splitHeaderToken) <= 1 {
204+
return nil, errNoAuthToken
205+
}
206+
reqToken = strings.TrimSpace(splitHeaderToken[1])
207+
} else {
208+
reqToken = strings.TrimSpace(tokenCookie.Value)
209+
}
210+
return swag.String(reqToken), nil
211+
}
212+
213+
func GetClaimsFromTokenInRequest(req *http.Request) (*DecryptedClaims, error) {
214+
sessionID, err := GetTokenFromRequest(req)
215+
if err != nil {
216+
return nil, err
217+
}
218+
// Perform decryption of the JWT, if MCS is able to decrypt the JWT that means a valid session
219+
// was used in the first place to get it
220+
claims, err := JWTAuthenticate(*sessionID)
221+
if err != nil {
222+
return nil, err
223+
}
224+
return claims, nil
225+
}

pkg/ws/websocket.go

Lines changed: 0 additions & 51 deletions
This file was deleted.

restapi/client.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,19 +239,18 @@ func newMinioClient(jwt string) (*minio.Client, error) {
239239
}
240240

241241
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
242-
func newS3BucketClient(jwt string, bucketName string) (*mc.S3Client, error) {
242+
func newS3BucketClient(claims *auth.DecryptedClaims, bucketName string) (*mc.S3Client, error) {
243243
endpoint := getMinIOServer()
244244
useSSL := getMinIOEndpointIsSecure()
245245

246-
claims, err := auth.JWTAuthenticate(jwt)
247-
if err != nil {
248-
return nil, err
249-
}
250-
251246
if strings.TrimSpace(bucketName) != "" {
252247
endpoint += fmt.Sprintf("/%s", bucketName)
253248
}
254249

250+
if claims == nil {
251+
return nil, fmt.Errorf("the provided credentials are invalid")
252+
}
253+
255254
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useSSL)
256255
client, pErr := mc.S3New(s3Config)
257256
if pErr != nil {

restapi/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package restapi
1818

1919
import (
2020
"fmt"
21+
"io/ioutil"
2122
"strconv"
2223
"strings"
2324

@@ -233,3 +234,23 @@ func getSecureExpectCTHeader() string {
233234
func getM3Host() string {
234235
return env.Get(McsM3Host, "http://m3:8787")
235236
}
237+
238+
// getMcsK8sServiceAccountJWTFromFile if mcs is running inside k8s
239+
// we check and extract the jwt sa-token from /var/run/secrets/kubernetes.io/serviceaccount/token, otherwise returns
240+
// empty string
241+
func getMcsK8sServiceAccountJWTFromFile() string {
242+
dat, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
243+
if err != nil {
244+
return ""
245+
}
246+
return string(dat)
247+
}
248+
249+
// This operation will run only once on mcs start-up
250+
var saToken = getMcsK8sServiceAccountJWTFromFile()
251+
252+
// getMcsK8sServiceAccountJWT return the ServiceAccount JWT mcs will use to interact with mkube, by
253+
// default will return the jwt sa-token associated to the pod running mcs inside kubernetes
254+
func getMcsK8sServiceAccountJWT() string {
255+
return env.Get(McsK8sServiceAccountJWT, saToken)
256+
}

restapi/configure_mcs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ func FileServerMiddleware(next http.Handler) http.Handler {
168168
serveWS(w, r)
169169
case strings.HasPrefix(r.URL.Path, "/api/v1/mkube"):
170170
client := &http.Client{}
171-
serverMkube(client, w, r)
171+
m3SAToken := getMcsK8sServiceAccountJWT()
172+
serverMkube(m3SAToken, client, w, r)
172173
case strings.HasPrefix(r.URL.Path, "/api"):
173174
next.ServeHTTP(w, r)
174175
default:

restapi/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ const (
5050
McsSecureFeaturePolicy = "MCS_SECURE_FEATURE_POLICY"
5151
McsSecureExpectCTHeader = "MCS_SECURE_EXPECT_CT_HEADER"
5252
McsM3Host = "MCS_M3_HOSTNAME"
53+
McsK8sServiceAccountJWT = "MCS_K8S_SA_JWT"
5354
)

restapi/mkube.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,21 @@ import (
2525
"strings"
2626

2727
apiErrors "github.com/go-openapi/errors"
28+
"github.com/minio/mcs/pkg/auth"
2829
)
2930

3031
// serverMkube handles calls for mkube
31-
func serverMkube(client *http.Client, w http.ResponseWriter, req *http.Request) {
32+
func serverMkube(m3SAToken string, client *http.Client, w http.ResponseWriter, req *http.Request) {
33+
// check user session is valid by decrypting the claims inside the encrypted JWT
34+
_, err := auth.GetClaimsFromTokenInRequest(req)
35+
if err != nil {
36+
apiErrors.ServeError(w, req, err)
37+
return
38+
}
39+
if m3SAToken == "" {
40+
apiErrors.ServeError(w, req, errors.New("service M3 is not available"))
41+
return
42+
}
3243
// destination of the request, the mkube server
3344
req.URL.Path = strings.Replace(req.URL.Path, "/mkube", "", 1)
3445
targetURL := fmt.Sprintf("%s%s", getM3Host(), req.URL.String())
@@ -41,8 +52,10 @@ func serverMkube(client *http.Client, w http.ResponseWriter, req *http.Request)
4152
return
4253
}
4354

44-
// set the m3Req headers
45-
m3Req.Header = req.Header
55+
// Set the m3Req authorization headers
56+
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
57+
token := fmt.Sprintf("Bearer %s", m3SAToken)
58+
m3Req.Header.Add("Authorization", token)
4659
resp, err := client.Do(m3Req)
4760
if err != nil {
4861
log.Println("error on m3 request:", err)

0 commit comments

Comments
 (0)