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

@@ -0,0 +1,40 @@
# MCS service account authentication with Mkube
`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
````

4
go.mod
View File

@@ -18,8 +18,8 @@ require (
github.com/json-iterator/go v1.1.9
github.com/minio/cli v1.22.0
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f
github.com/minio/minio v0.0.0-20200603201854-5686a7e27319
github.com/minio/minio-go/v6 v6.0.56
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.5.1

50
go.sum
View File

@@ -15,6 +15,7 @@ github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -47,10 +48,14 @@ github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb v1.0.28 h1:kWGpdAcSp3MxMU9CCHOwz/8V0kCHN4+9yQm2MzWuI98=
@@ -60,6 +65,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/colinmarc/hdfs/v2 v2.1.1 h1:x0hw/m+o3UE20Scso/KCkvYNc9Di39TBlCfGMkJ1/a0=
github.com/colinmarc/hdfs/v2 v2.1.1/go.mod h1:M3x+k8UKKmxtFu++uAZ0OtDU8jR3jnaZIAc6yK4Ue0c=
github.com/coredns/coredns v1.4.0 h1:RubBkYmkByUqZWWkjRHvNLnUHgkRVqAWgSMmRFvpE1A=
github.com/coredns/coredns v1.4.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
@@ -74,11 +81,13 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/djherbis/atime v1.0.0 h1:ySLvBAM0EvOGaX7TI4dAM5lWj+RdJUCKtGSEHN8SGBg=
github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -116,6 +125,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
@@ -232,6 +242,8 @@ github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNu
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
@@ -291,6 +303,7 @@ github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+0
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-uuid v0.0.0-20180228145832-27454136f036/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -312,6 +325,7 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKe
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
@@ -359,6 +373,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7 h1:smZXPopqRVVywwzou4WYWvUbJvSAzIDFizfWElpmAqY=
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
@@ -407,13 +423,14 @@ github.com/minio/lsync v1.0.1/go.mod h1:tCFzfo0dlvdGl70IT4IAK/5Wtgb0/BrTmo/jE8pA
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6 h1:2SrKe2vLDLwvnYkYrJelrzyGW8t/8HCbr9yDsw+8XSI=
github.com/minio/mc v0.0.0-20200515235434-3b479cf92ed6/go.mod h1:U3Jgk0bcSjn+QPUMisrS6nxCWOoQ6rYWSvLCB30apuU=
github.com/minio/minio v0.0.0-20200421050159-282c9f790a03/go.mod h1:zBua5AiljGs1Irdl2XEyiJjvZVCVDIG8gjozzRBcVlw=
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb h1:CQC7D3UDnUycuxhwImcVhMSLet/RbShosAnYcvMtEB8=
github.com/minio/minio v0.0.0-20200516011754-9cac385aecdb/go.mod h1:wymaytM/HELuwdz7BGZHmQ3XKq2SxPsLeGxyOCaCLiA=
github.com/minio/minio v0.0.0-20200603201854-5686a7e27319 h1:Vh7ATrN/BDjUx9XBKg5fpqSn2LPV/9BKKdldu31+2HY=
github.com/minio/minio v0.0.0-20200603201854-5686a7e27319/go.mod h1:hW3OqYRO05rdeeuHHTif+B7WYxtDB3/hZb7eKfHXy1Y=
github.com/minio/minio-go/v6 v6.0.53 h1:8jzpwiOzZ5Iz7/goFWqNZRICbyWYShbb5rARjrnSCNI=
github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI=
github.com/minio/minio-go/v6 v6.0.55-0.20200425081427-89eebdef2af0/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f h1:ifHrI8+exqLi5RztIWWKS5k+Wu+W7DJisVXwNaCH2zs=
github.com/minio/minio-go/v6 v6.0.56-0.20200502013257-a81c8c13cc3f/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
github.com/minio/minio-go/v6 v6.0.56 h1:H4+v6UFV1V7VkEf1HjL15W9OvTL1Gy8EbMmjQZHqEbg=
github.com/minio/minio-go/v6 v6.0.56/go.mod h1:KQMM+/44DSlSGSQWSfRrAZ12FVMmpWNuX37i2AX0jfI=
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61 h1:pUSI/WKPdd77gcuoJkSzhJ4wdS8OMDOsOu99MtpXEQA=
github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61/go.mod h1:4trzEJ7N1nBTd5Tt7OCZT5SEin+WiAXpdJ/WgPkESA8=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
@@ -470,9 +487,12 @@ github.com/nats-io/stan.go v0.4.5 h1:lPZ9y1jVGiXcTaUc1SnEIWPYfh0avuEiHBePNJYgpPk
github.com/nats-io/stan.go v0.4.5/go.mod h1:Ji7mK6gRZJSH1nc3ZJH6vi7zn/QnZhpR9Arm4iuzsUQ=
github.com/ncw/directio v1.0.5 h1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=
github.com/ncw/directio v1.0.5/go.mod h1:rX/pKEYkOXBGOggmcyJeJGloCkleSvphPx2eV3t6ROk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY=
github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -503,17 +523,25 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81 h1:zQTtDd7fQiF9e80lbl+ShnD9/5NSq5r1EhcS8955ECg=
github.com/rcrowley/go-metrics v0.0.0-20190704165056-9c2d0518ed81/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -548,8 +576,11 @@ github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@@ -627,6 +658,7 @@ go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -739,8 +771,9 @@ golang.org/x/tools v0.0.0-20190914235951-31e00f45c22e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 h1:Xvf3ZQTm5bjXPxhI7g+dwqsCqadK1rcNtwtszuatetk=
golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770 h1:M9Fif0OxNji8w+HvmhVQ8KJtiZOsjU9RgslJGhn95XE=
golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@@ -770,6 +803,8 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -778,6 +813,7 @@ gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4Dl
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
@@ -788,6 +824,8 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXt
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/gokrb5.v7 v7.3.0 h1:0709Jtq/6QXEuWRfAm260XqlpcwL1vxtO1tUE2qK8Z4=
gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ldap.v3 v3.0.3 h1:YKRHW/2sIl05JsCtx/5ZuUueFuJyoj/6+DGXe3wp6ro=
@@ -809,4 +847,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -37,7 +37,7 @@ import (
type LoginDetails struct {
// login strategy
// Enum: [form redirect]
// Enum: [form redirect service-account]
LoginStrategy string `json:"loginStrategy,omitempty"`
// redirect
@@ -62,7 +62,7 @@ var loginDetailsTypeLoginStrategyPropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["form","redirect"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["form","redirect","service-account"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
@@ -77,6 +77,9 @@ const (
// LoginDetailsLoginStrategyRedirect captures enum value "redirect"
LoginDetailsLoginStrategyRedirect string = "redirect"
// LoginDetailsLoginStrategyServiceAccount captures enum value "service-account"
LoginDetailsLoginStrategyServiceAccount string = "service-account"
)
// prop value enum

View File

@@ -0,0 +1,81 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// LoginMkubeRequest login mkube request
//
// swagger:model loginMkubeRequest
type LoginMkubeRequest struct {
// jwt
// Required: true
Jwt *string `json:"jwt"`
}
// Validate validates this login mkube request
func (m *LoginMkubeRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateJwt(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *LoginMkubeRequest) validateJwt(formats strfmt.Registry) error {
if err := validate.Required("jwt", "body", m.Jwt); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *LoginMkubeRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *LoginMkubeRequest) UnmarshalBinary(b []byte) error {
var res LoginMkubeRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -22,8 +22,8 @@ import (
"github.com/minio/minio/pkg/env"
)
// GetOperatorOnly gets mcs operator mode status set on env variable
//or default one
func GetOperatorOnly() string {
return strings.ToLower(env.Get(McsOperatorOnly, "off"))
// GetOperatorOnly gets MCS mkube admin mode status set on env variable
// or default one
func GetOperatorOnly() bool {
return strings.ToLower(env.Get(McsmKubeAdminOnly, "off")) == "on"
}

View File

@@ -17,5 +17,5 @@
package acl
const (
McsOperatorOnly = "MCS_OPERATOR_ONLY"
McsmKubeAdminOnly = "MCS_MKUBE_ADMIN_ONLY"
)

View File

@@ -286,7 +286,7 @@ func actionsStringToActionSet(actions []string) iampolicy.ActionSet {
func GetAuthorizedEndpoints(actions []string) []string {
rangeTake := endpointRules
if operatorOnly == "on" {
if operatorOnly {
rangeTake = operatorRules
}

View File

@@ -106,7 +106,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
}
func TestOperatorOnlyEndpoints(t *testing.T) {
operatorOnly = "on"
operatorOnly = true
tests := []endpoint{
{

View File

@@ -26,9 +26,11 @@ import (
"fmt"
"io"
"log"
"net/http"
"strings"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/go-openapi/swag"
xjwt "github.com/minio/mcs/pkg/auth/jwt"
"github.com/minio/minio-go/v6/pkg/credentials"
"github.com/minio/minio/cmd"
@@ -182,3 +184,42 @@ func decrypt(data []byte) ([]byte, error) {
}
return plaintext, nil
}
// GetTokenFromRequest returns a token from a http Request
// either defined on a cookie `token` or on Authorization header.
//
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
func GetTokenFromRequest(r *http.Request) (*string, error) {
// Get Auth token
var reqToken string
// Token might come either as a Cookie or as a Header
// if not set in cookie, check if it is set on Header.
tokenCookie, err := r.Cookie("token")
if err != nil {
headerToken := r.Header.Get("Authorization")
// reqToken should come as "Bearer <token>"
splitHeaderToken := strings.Split(headerToken, "Bearer")
if len(splitHeaderToken) <= 1 {
return nil, errNoAuthToken
}
reqToken = strings.TrimSpace(splitHeaderToken[1])
} else {
reqToken = strings.TrimSpace(tokenCookie.Value)
}
return swag.String(reqToken), nil
}
func GetClaimsFromTokenInRequest(req *http.Request) (*DecryptedClaims, error) {
sessionID, err := GetTokenFromRequest(req)
if err != nil {
return nil, err
}
// Perform decryption of the JWT, if MCS is able to decrypt the JWT that means a valid session
// was used in the first place to get it
claims, err := JWTAuthenticate(*sessionID)
if err != nil {
return nil, err
}
return claims, nil
}

77
pkg/auth/mkube.go Normal file
View File

@@ -0,0 +1,77 @@
// 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 auth
import (
"fmt"
"log"
"net/http"
"github.com/minio/mcs/pkg/auth/mkube"
"github.com/minio/minio-go/v6/pkg/credentials"
)
// mkubeCredentialsProvider is an struct to hold the JWT (service account token)
type mkubeCredentialsProvider struct {
serviceAccountJWT string
}
// Implementing the interfaces of the minio Provider, we use this to leverage on the existing mcs Authentication flow
func (s mkubeCredentialsProvider) Retrieve() (credentials.Value, error) {
return credentials.Value{
AccessKeyID: "",
SecretAccessKey: "",
SessionToken: s.serviceAccountJWT,
}, nil
}
// IsExpired dummy function, must be implemented in order to work with the minio provider authentication
func (s mkubeCredentialsProvider) IsExpired() bool {
return false
}
// isServiceAccountTokenValid will make an authenticated request (using bearer token) against Mkube hostname, if the
// request success means the provided jwt its a valid service account token and the MCS user can use it for future requests
// until it fails
func isServiceAccountTokenValid(client *http.Client, jwt string) bool {
url := fmt.Sprintf("%s/api/v1/tenants", mkube.GetMkubeEndpoint())
m3Req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println(err)
return false
}
token := fmt.Sprintf("Bearer %s", jwt)
m3Req.Header.Add("Authorization", token)
resp, err := client.Do(m3Req)
if err != nil {
log.Println(err)
return false
}
defer resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return true
}
return false
}
// GetMcsCredentialsFromMkube will validate the provided JWT (service account token) and return it in the form of credentials.Credentials
func GetMcsCredentialsFromMkube(jwt string) (*credentials.Credentials, error) {
if isServiceAccountTokenValid(mkube.HTTPClient, jwt) {
return credentials.New(mkubeCredentialsProvider{serviceAccountJWT: jwt}), nil
}
return nil, errInvalidCredentials
}

117
pkg/auth/mkube/config.go Normal file
View File

@@ -0,0 +1,117 @@
// 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 mkube
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/minio/minio/pkg/env"
)
var (
certDontExists = "File certificate doesn't exists: %s"
)
// getMkubeEndpoint returns the hostname of mkube
func GetMkubeEndpoint() string {
return env.Get(McsMkubeHost, "http://m3:8787")
}
// getMkubeEndpointIsSecure returns true or false depending on the protocol in Mkube URL
func getMkubeEndpointIsSecure() bool {
server := GetMkubeEndpoint()
if strings.Contains(server, "://") {
parts := strings.Split(server, "://")
if len(parts) > 1 {
if parts[0] == "https" {
return true
}
}
}
return false
}
// If MCS_M3_SERVER_TLS_CA_CERTIFICATE is true mcs will load a list of certificates into the
// http.client rootCAs store, this is useful for testing or when working with self-signed certificates
func getMkubeServerTLSRootCAs() []string {
caCertFileNames := strings.TrimSpace(env.Get(McsMkubeTLSCACertificate, ""))
if caCertFileNames == "" {
return []string{}
}
return strings.Split(caCertFileNames, ",")
}
// FileExists verifies if a file exist on the desired location and its not a folder
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// GetMkubeHTTPClient returns an http.Client with custom configurations used by MCS to talk to Mkube
// custom configurations include the use of CA certificates
func getMkubeHTTPClient() *http.Client {
httpTransport := &http.Transport{}
// If Mkube server is running with TLS enabled and it's using a self-signed certificate
// or a certificate issued by a custom certificate authority we prepare a new custom *http.Transport
if getMkubeEndpointIsSecure() {
caCertFileNames := getMkubeServerTLSRootCAs()
tlsConfig := &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
}
// If CAs certificates are configured we save them to the http.Client RootCAs store
if len(caCertFileNames) > 0 {
certs := x509.NewCertPool()
for _, caCert := range caCertFileNames {
// Validate certificate exists
if FileExists(caCert) {
pemData, err := ioutil.ReadFile(caCert)
if err != nil {
// if there was an error reading pem file stop mcs
panic(err)
}
certs.AppendCertsFromPEM(pemData)
} else {
// if provided cert filename doesn't exists stop mcs
panic(fmt.Sprintf(certDontExists, caCert))
}
}
tlsConfig.RootCAs = certs
}
httpTransport.TLSClientConfig = tlsConfig
}
// Return http client with default configuration
return &http.Client{
Transport: httpTransport,
}
}
// HTTPClient it's a public variable that contains the HTTP configuration to be used by MCS to talk to Mkube
// This function will run only once
var HTTPClient = getMkubeHTTPClient()

22
pkg/auth/mkube/const.go Normal file
View File

@@ -0,0 +1,22 @@
// 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 mkube
const (
McsMkubeHost = "MCS_M3_HOSTNAME"
McsMkubeTLSCACertificate = "MCS_M3_SERVER_TLS_CA_CERTIFICATE"
)

77
pkg/auth/mkube_test.go Normal file
View File

@@ -0,0 +1,77 @@
package auth
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"testing"
)
// 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,
}
}
func Test_isServiceAccountTokenValid(t *testing.T) {
successResponse := NewTestClient(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)),
Header: make(http.Header),
}, nil
})
failResponse := 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")
})
type args struct {
client *http.Client
jwt string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Success authentication - correct jwt (service account token)",
args: args{
client: successResponse,
jwt: "GOODTOKEN",
},
want: true,
},
{
name: "Fail authentication - incorrect jwt (service account token)",
args: args{
client: failResponse,
jwt: "BADTOKEN",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isServiceAccountTokenValid(tt.args.client, tt.args.jwt); got != tt.want {
t.Errorf("isServiceAccountTokenValid() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,51 +0,0 @@
// 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 ws contains websocket utils for mcs project
package ws
import (
"net/http"
"strings"
"github.com/go-openapi/errors"
"github.com/go-openapi/swag"
)
// GetTokenFromRequest returns a token from a http Request
// either defined on a cookie `token` or on Authorization header.
//
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
func GetTokenFromRequest(r *http.Request) (*string, error) {
// Get Auth token
var reqToken string
// Token might come either as a Cookie or as a Header
// if not set in cookie, check if it is set on Header.
tokenCookie, err := r.Cookie("token")
if err != nil {
headerToken := r.Header.Get("Authorization")
// reqToken should come as "Bearer <token>"
splitHeaderToken := strings.Split(headerToken, "Bearer")
if len(splitHeaderToken) <= 1 {
return nil, errors.New(http.StatusBadRequest, "Authentication not valid")
}
reqToken = strings.TrimSpace(splitHeaderToken[1])
} else {
reqToken = strings.TrimSpace(tokenCookie.Value)
}
return swag.String(reqToken), nil
}

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
// 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/>.
import React from "react";
import React, { useEffect, useState } from "react";
import request from "superagent";
import storage from "local-storage-fallback";
import { connect, ConnectedProps } from "react-redux";
@@ -26,9 +26,8 @@ import { CircularProgress, Paper } from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { SystemState } from "../../types";
import { userLoggedIn } from "../../actions";
import history from "../../history";
import api from "../../common/api";
import { ILoginDetails } from "./types";
import { ILoginDetails, loginStrategyType } from "./types";
import { setCookie } from "../../common/utils";
const styles = (theme: Theme) =>
@@ -101,57 +100,57 @@ interface ILoginProps {
classes: any;
}
interface ILoginState {
accessKey: string;
secretKey: string;
error: string;
loading: boolean;
loginStrategy: ILoginDetails;
interface LoginStrategyRoutes {
[key: string]: string;
}
class Login extends React.Component<ILoginProps, ILoginState> {
state: ILoginState = {
accessKey: "",
secretKey: "",
error: "",
loading: false,
loginStrategy: {
loginStrategy: "",
redirect: "",
},
};
interface LoginStrategyPayload {
[key: string]: any;
}
fetchConfiguration() {
this.setState({ loading: true }, () => {
api
.invoke("GET", "/api/v1/login")
.then((loginDetails: ILoginDetails) => {
this.setState({
loading: false,
});
this.setState({
loading: false,
loginStrategy: loginDetails,
error: "",
});
})
.catch((err: any) => {
this.setState({ loading: false, error: err });
});
});
const Login = ({ classes, userLoggedIn }: ILoginProps) => {
const [accessKey, setAccessKey] = useState<string>("");
const [jwt, setJwt] = useState<string>("");
const [secretKey, setSecretKey] = useState<string>("");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [loginStrategy, setLoginStrategy] = useState<ILoginDetails>({
loginStrategy: loginStrategyType.unknown,
redirect: "",
});
const loginStrategyEndpoints: LoginStrategyRoutes = {
"form": "/api/v1/login",
"service-account": "/api/v1/login/mkube",
}
const loginStrategyPayload: LoginStrategyPayload = {
"form": { accessKey, secretKey },
"service-account": { jwt },
}
formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const url = "/api/v1/login";
const { accessKey, secretKey } = this.state;
const fetchConfiguration = () => {
setLoading(true);
api
.invoke("GET", "/api/v1/login")
.then((loginDetails: ILoginDetails) => {
setLoading(false);
setLoginStrategy(loginDetails);
setError("");
})
.catch((err: any) => {
setLoading(false);
setError(err);
});
};
const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
request
.post(url)
.send({ accessKey, secretKey })
.post(loginStrategyEndpoints[loginStrategy.loginStrategy] || "/api/v1/login")
.send(loginStrategyPayload[loginStrategy.loginStrategy])
.then((res: any) => {
const bodyResponse = res.body;
if (bodyResponse.sessionId) {
// store the jwt token
setCookie("token", bodyResponse.sessionId);
@@ -164,134 +163,173 @@ class Login extends React.Component<ILoginProps, ILoginState> {
})
.then(() => {
// We set the state in redux
this.props.userLoggedIn(true);
userLoggedIn(true);
// There is a browser cache issue if we change the policy associated to an account and then logout and history.push("/") after login
// therefore after login we need to use window.location redirect
window.location.href = "/";
})
.catch((err) => {
this.setState({ error: `${err}` });
setError(err.message);
});
};
componentDidMount(): void {
this.fetchConfiguration();
}
useEffect(() => {
fetchConfiguration();
}, []);
render() {
const { error, accessKey, secretKey, loginStrategy } = this.state;
const { classes } = this.props;
let loginComponent = null;
let loginComponent = null;
switch (loginStrategy.loginStrategy) {
case "form": {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<form
className={classes.form}
noValidate
onSubmit={this.formSubmit}
>
<Grid container spacing={2}>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
switch (loginStrategy.loginStrategy) {
case loginStrategyType.form: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<form className={classes.form} noValidate onSubmit={formSubmit}>
<Grid container spacing={2}>
{error !== "" && (
<Grid item xs={12}>
<TextField
required
fullWidth
id="accessKey"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ accessKey: e.target.value })
}
label="Access Key"
name="accessKey"
autoComplete="username"
/>
</Grid>
<Grid item xs={12}>
<TextField
required
fullWidth
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ secretKey: e.target.value })
}
name="secretKey"
label="Secret Key"
type="password"
id="secretKey"
autoComplete="current-password"
/>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<TextField
required
fullWidth
id="accessKey"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setAccessKey(e.target.value)
}
label="Access Key"
name="accessKey"
autoComplete="username"
/>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Login
</Button>
</form>
</React.Fragment>
);
break;
}
case "redirect": {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<Grid item xs={12}>
<TextField
required
fullWidth
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSecretKey(e.target.value)
}
name="secretKey"
label="Secret Key"
type="password"
id="secretKey"
autoComplete="current-password"
/>
</Grid>
</Grid>
<Button
component={"a"}
href={loginStrategy.redirect}
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Welcome
Login
</Button>
</React.Fragment>
);
break;
}
default:
loginComponent = (
<CircularProgress className={classes.loadingLoginStrategy} />
);
</form>
</React.Fragment>
);
break;
}
return (
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
</Grid>
<Grid item xs={5} className={classes.theLogin}>
{loginComponent}
</Grid>
</Grid>
</Paper>
);
case loginStrategyType.redirect: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<Button
component={"a"}
href={loginStrategy.redirect}
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Welcome
</Button>
</React.Fragment>
);
break;
}
case loginStrategyType.serviceAccount: {
loginComponent = (
<React.Fragment>
<Typography component="h1" variant="h6">
Login
</Typography>
<form className={classes.form} noValidate onSubmit={formSubmit}>
<Grid container spacing={2}>
{error !== "" && (
<Grid item xs={12}>
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{error}
</Typography>
</Grid>
)}
<Grid item xs={12}>
<TextField
required
fullWidth
id="jwt"
value={jwt}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setJwt(e.target.value)
}
label="JWT"
name="jwt"
autoComplete="Service Account JWT Token"
/>
</Grid>
</Grid>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Login
</Button>
</form>
</React.Fragment>
);
break;
}
default:
loginComponent = (
<CircularProgress className={classes.loadingLoginStrategy} />
);
}
}
return (
<Paper className={classes.paper}>
<Grid container className={classes.mainContainer}>
<Grid item xs={7} className={classes.theOcean}>
<div className={classes.oceanBg} />
</Grid>
<Grid item xs={5} className={classes.theLogin}>
{loginComponent}
</Grid>
</Grid>
</Paper>
);
};
export default connector(withStyles(styles)(Login));

View File

@@ -15,6 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface ILoginDetails {
loginStrategy: string;
loginStrategy: loginStrategyType;
redirect: string;
}
export enum loginStrategyType {
unknown = "unknown",
form = "form",
redirect = "redirect",
serviceAccount = "service-account",
}

View File

@@ -25,9 +25,11 @@ import (
mc "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/mcs/pkg/acl"
"github.com/minio/mcs/pkg/auth"
xjwt "github.com/minio/mcs/pkg/auth/jwt"
"github.com/minio/mcs/pkg/auth/ldap"
"github.com/minio/mcs/pkg/auth/mkube"
"github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/credentials"
)
@@ -120,7 +122,7 @@ func (c mcS3Client) watch(options mc.WatchOptions) (*mc.WatchObject, *probe.Erro
}
// MCSCredentials interface with all functions to be implemented
// by mock when testing, it should include all needed minioCredentials.Credentials api calls
// by mock when testing, it should include all needed mcsCredentials.Credentials api calls
// that are used within this project.
type MCSCredentials interface {
Get() (credentials.Value, error)
@@ -129,17 +131,17 @@ type MCSCredentials interface {
// Interface implementation
type mcsCredentials struct {
minioCredentials *credentials.Credentials
mcsCredentials *credentials.Credentials
}
// implements *Credentials.Get()
func (c mcsCredentials) Get() (credentials.Value, error) {
return c.minioCredentials.Get()
return c.mcsCredentials.Get()
}
// implements *Credentials.Expire()
func (c mcsCredentials) Expire() {
c.minioCredentials.Expire()
c.mcsCredentials.Expire()
}
// mcsSTSAssumeRole it's a STSAssumeRole wrapper, in general
@@ -159,22 +161,31 @@ func (s mcsSTSAssumeRole) IsExpired() bool {
// STSClient contains http.client configuration need it by STSAssumeRole
var STSClient = PrepareSTSClient()
var MinioEndpoint = getMinIOServer()
var MkubeEndpoint = mkube.GetMkubeEndpoint()
func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
mcsEndpoint := getMinIOServer()
if mcsEndpoint == "" {
return nil, errors.New("endpoint cannot be empty for AssumeRoleSTS")
}
if accessKey == "" || secretKey == "" {
return nil, errors.New("creredentials access/secretkey is mandatory for AssumeRoleSTS")
}
// Future authentication methods can be added under this switch statement
switch {
// MKUBE authentication for MCS
case acl.GetOperatorOnly():
{
if MkubeEndpoint == "" {
return nil, errors.New("endpoint cannot be empty for Mkube")
}
creds, err := auth.GetMcsCredentialsFromMkube(secretKey)
if err != nil {
return nil, err
}
return creds, nil
}
// LDAP authentication for MCS
case ldap.GetLDAPEnabled():
{
creds, err := auth.GetMcsCredentialsFromLDAP(mcsEndpoint, accessKey, secretKey)
if MinioEndpoint == "" {
return nil, errors.New("endpoint cannot be empty for AssumeRoleSTS")
}
creds, err := auth.GetMcsCredentialsFromLDAP(MinioEndpoint, accessKey, secretKey)
if err != nil {
return nil, err
}
@@ -183,6 +194,9 @@ func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Cred
// default authentication for MCS is via STS (Security Token Service) against MinIO
default:
{
if MinioEndpoint == "" || accessKey == "" || secretKey == "" {
return nil, errors.New("creredentials endpont, access and secretkey are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
@@ -191,7 +205,7 @@ func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Cred
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: STSClient,
STSEndpoint: mcsEndpoint,
STSEndpoint: MinioEndpoint,
Options: opts,
}
mcsSTSWrapper := mcsSTSAssumeRole{stsAssumeRole: stsAssumeRole}
@@ -209,7 +223,7 @@ func GetClaimsFromJWT(jwt string) (*auth.DecryptedClaims, error) {
return claims, nil
}
// getMcsCredentialsFromJWT returns the *minioCredentials.Credentials associated to the
// getMcsCredentialsFromJWT returns the *mcsCredentials.Credentials associated to the
// provided jwt, this is useful for running the Expire() or IsExpired() operations
func getMcsCredentialsFromJWT(jwt string) (*credentials.Credentials, error) {
claims, err := GetClaimsFromJWT(jwt)
@@ -220,7 +234,7 @@ func getMcsCredentialsFromJWT(jwt string) (*credentials.Credentials, error) {
return creds, nil
}
// newMinioClient creates a new MinIO client based on the minioCredentials extracted
// newMinioClient creates a new MinIO client based on the mcsCredentials extracted
// from the provided jwt
func newMinioClient(jwt string) (*minio.Client, error) {
creds, err := getMcsCredentialsFromJWT(jwt)
@@ -239,19 +253,18 @@ func newMinioClient(jwt string) (*minio.Client, error) {
}
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
func newS3BucketClient(jwt string, bucketName string) (*mc.S3Client, error) {
func newS3BucketClient(claims *auth.DecryptedClaims, bucketName string) (*mc.S3Client, error) {
endpoint := getMinIOServer()
useSSL := getMinIOEndpointIsSecure()
claims, err := auth.JWTAuthenticate(jwt)
if err != nil {
return nil, err
}
if strings.TrimSpace(bucketName) != "" {
endpoint += fmt.Sprintf("/%s", bucketName)
}
if claims == nil {
return nil, fmt.Errorf("the provided credentials are invalid")
}
s3Config := newS3Config(endpoint, claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken, !useSSL)
client, pErr := mc.S3New(s3Config)
if pErr != nil {
@@ -269,7 +282,7 @@ func newS3BucketClient(jwt string, bucketName string) (*mc.S3Client, error) {
// parameters.
func newS3Config(endpoint, accessKey, secretKey, sessionToken string, isSecure bool) *mc.Config {
// We have a valid alias and hostConfig. We populate the
// minioCredentials from the match found in the config file.
// mcsCredentials from the match found in the config file.
s3Config := new(mc.Config)
s3Config.AppName = "mcs" // TODO: make this a constant

View File

@@ -228,8 +228,3 @@ func getSecureFeaturePolicy() string {
func getSecureExpectCTHeader() string {
return env.Get(McsSecureExpectCTHeader, "")
}
// getM3Host returns the hostname of mkube
func getM3Host() string {
return env.Get(McsM3Host, "http://m3:8787")
}

View File

@@ -27,6 +27,7 @@ import (
"github.com/minio/mcs/models"
"github.com/minio/mcs/pkg"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/pkg/auth/mkube"
assetFS "github.com/elazarl/go-bindata-assetfs"
@@ -167,8 +168,7 @@ func FileServerMiddleware(next http.Handler) http.Handler {
case strings.HasPrefix(r.URL.Path, "/ws"):
serveWS(w, r)
case strings.HasPrefix(r.URL.Path, "/api/v1/mkube"):
client := &http.Client{}
serverMkube(client, w, r)
serverMkube(mkube.HTTPClient, w, r)
case strings.HasPrefix(r.URL.Path, "/api"):
next.ServeHTTP(w, r)
default:

View File

@@ -49,5 +49,4 @@ const (
McsSecureReferrerPolicy = "MCS_SECURE_REFERRER_POLICY"
McsSecureFeaturePolicy = "MCS_SECURE_FEATURE_POLICY"
McsSecureExpectCTHeader = "MCS_SECURE_EXPECT_CT_HEADER"
McsM3Host = "MCS_M3_HOSTNAME"
)

View File

@@ -754,6 +754,40 @@ func init() {
}
}
},
"/login/mkube": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Mkube.",
"operationId": "LoginMkube",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginMkubeRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/oauth2/auth": {
"post": {
"security": [],
@@ -1719,7 +1753,8 @@ func init() {
"type": "string",
"enum": [
"form",
"redirect"
"redirect",
"service-account"
]
},
"redirect": {
@@ -1727,6 +1762,17 @@ func init() {
}
}
},
"loginMkubeRequest": {
"type": "object",
"required": [
"jwt"
],
"properties": {
"jwt": {
"type": "string"
}
}
},
"loginOauth2AuthRequest": {
"type": "object",
"required": [
@@ -2869,6 +2915,40 @@ func init() {
}
}
},
"/login/mkube": {
"post": {
"security": [],
"tags": [
"UserAPI"
],
"summary": "Login to Mkube.",
"operationId": "LoginMkube",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/loginMkubeRequest"
}
}
],
"responses": {
"201": {
"description": "A successful login.",
"schema": {
"$ref": "#/definitions/loginResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/login/oauth2/auth": {
"post": {
"security": [],
@@ -3834,7 +3914,8 @@ func init() {
"type": "string",
"enum": [
"form",
"redirect"
"redirect",
"service-account"
]
},
"redirect": {
@@ -3842,6 +3923,17 @@ func init() {
}
}
},
"loginMkubeRequest": {
"type": "object",
"required": [
"jwt"
],
"properties": {
"jwt": {
"type": "string"
}
}
},
"loginOauth2AuthRequest": {
"type": "object",
"required": [

View File

@@ -18,6 +18,7 @@ package restapi
import (
"bufio"
"bytes"
"errors"
"fmt"
"log"
@@ -25,24 +26,45 @@ import (
"strings"
apiErrors "github.com/go-openapi/errors"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/pkg/auth/mkube"
)
// serverMkube handles calls for mkube
func serverMkube(client *http.Client, w http.ResponseWriter, req *http.Request) {
// extract the service account token inside the jwt encrypted claims
claims, err := auth.GetClaimsFromTokenInRequest(req)
if err != nil {
apiErrors.ServeError(w, req, err)
return
}
m3SAToken := claims.SessionToken
if m3SAToken == "" {
apiErrors.ServeError(w, req, errors.New("service M3 is not available"))
return
}
// destination of the request, the mkube server
req.URL.Path = strings.Replace(req.URL.Path, "/mkube", "", 1)
targetURL := fmt.Sprintf("%s%s", getM3Host(), req.URL.String())
targetURL := fmt.Sprintf("%s%s", mkube.GetMkubeEndpoint(), req.URL.String())
body := new(bytes.Buffer)
_, err = body.ReadFrom(req.Body)
if err != nil {
apiErrors.ServeError(w, req, err)
return
}
// set the HTTP method, url, and m3Req body
m3Req, err := http.NewRequest(req.Method, targetURL, req.Body)
m3Req, err := http.NewRequest(req.Method, targetURL, body)
if err != nil {
apiErrors.ServeError(w, req, err)
log.Println("error creating m3 request:", err)
return
}
// set the m3Req headers
m3Req.Header = req.Header
// Set the m3Req authorization headers
// Authorization Header needs to be like "Authorization Bearer <jwt_token>"
token := fmt.Sprintf("Bearer %s", m3SAToken)
m3Req.Header.Add("Authorization", token)
m3Req.Header.Add("Content-type", "application/json; charset=utf-8")
resp, err := client.Do(m3Req)
if err != nil {
log.Println("error on m3 request:", err)

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
}
})

View File

@@ -141,6 +141,9 @@ func NewMcsAPI(spec *loads.Document) *McsAPI {
UserAPILoginDetailHandler: user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginDetail has not yet been implemented")
}),
UserAPILoginMkubeHandler: user_api.LoginMkubeHandlerFunc(func(params user_api.LoginMkubeParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginMkube has not yet been implemented")
}),
UserAPILoginOauth2AuthHandler: user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
return middleware.NotImplemented("operation user_api.LoginOauth2Auth has not yet been implemented")
}),
@@ -293,6 +296,8 @@ type McsAPI struct {
UserAPILoginHandler user_api.LoginHandler
// UserAPILoginDetailHandler sets the operation handler for the login detail operation
UserAPILoginDetailHandler user_api.LoginDetailHandler
// UserAPILoginMkubeHandler sets the operation handler for the login mkube operation
UserAPILoginMkubeHandler user_api.LoginMkubeHandler
// UserAPILoginOauth2AuthHandler sets the operation handler for the login oauth2 auth operation
UserAPILoginOauth2AuthHandler user_api.LoginOauth2AuthHandler
// UserAPILogoutHandler sets the operation handler for the logout operation
@@ -478,6 +483,9 @@ func (o *McsAPI) Validate() error {
if o.UserAPILoginDetailHandler == nil {
unregistered = append(unregistered, "user_api.LoginDetailHandler")
}
if o.UserAPILoginMkubeHandler == nil {
unregistered = append(unregistered, "user_api.LoginMkubeHandler")
}
if o.UserAPILoginOauth2AuthHandler == nil {
unregistered = append(unregistered, "user_api.LoginOauth2AuthHandler")
}
@@ -736,6 +744,10 @@ func (o *McsAPI) initHandlerCache() {
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/mkube"] = user_api.NewLoginMkube(o.context, o.UserAPILoginMkubeHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)
}
o.handlers["POST"]["/login/oauth2/auth"] = user_api.NewLoginOauth2Auth(o.context, o.UserAPILoginOauth2AuthHandler)
if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler)

View File

@@ -0,0 +1,75 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
)
// LoginMkubeHandlerFunc turns a function with the right signature into a login mkube handler
type LoginMkubeHandlerFunc func(LoginMkubeParams) middleware.Responder
// Handle executing the request and returning a response
func (fn LoginMkubeHandlerFunc) Handle(params LoginMkubeParams) middleware.Responder {
return fn(params)
}
// LoginMkubeHandler interface for that can handle valid login mkube params
type LoginMkubeHandler interface {
Handle(LoginMkubeParams) middleware.Responder
}
// NewLoginMkube creates a new http.Handler for the login mkube operation
func NewLoginMkube(ctx *middleware.Context, handler LoginMkubeHandler) *LoginMkube {
return &LoginMkube{Context: ctx, Handler: handler}
}
/*LoginMkube swagger:route POST /login/mkube UserAPI loginMkube
Login to Mkube.
*/
type LoginMkube struct {
Context *middleware.Context
Handler LoginMkubeHandler
}
func (o *LoginMkube) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
r = rCtx
}
var Params = NewLoginMkubeParams()
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,94 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"io"
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/mcs/models"
)
// NewLoginMkubeParams creates a new LoginMkubeParams object
// no default values defined in spec.
func NewLoginMkubeParams() LoginMkubeParams {
return LoginMkubeParams{}
}
// LoginMkubeParams contains all the bound params for the login mkube operation
// typically these are obtained from a http.Request
//
// swagger:parameters LoginMkube
type LoginMkubeParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: body
*/
Body *models.LoginMkubeRequest
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewLoginMkubeParams() beforehand.
func (o *LoginMkubeParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
if runtime.HasBody(r) {
defer r.Body.Close()
var body models.LoginMkubeRequest
if err := route.Consumer.Consume(r.Body, &body); err != nil {
if err == io.EOF {
res = append(res, errors.Required("body", "body"))
} else {
res = append(res, errors.NewParseError("body", "body", "", err))
}
} else {
// validate body object
if err := body.Validate(route.Formats); err != nil {
res = append(res, err)
}
if len(res) == 0 {
o.Body = &body
}
}
} else {
res = append(res, errors.Required("body", "body"))
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/mcs/models"
)
// LoginMkubeCreatedCode is the HTTP code returned for type LoginMkubeCreated
const LoginMkubeCreatedCode int = 201
/*LoginMkubeCreated A successful login.
swagger:response loginMkubeCreated
*/
type LoginMkubeCreated struct {
/*
In: Body
*/
Payload *models.LoginResponse `json:"body,omitempty"`
}
// NewLoginMkubeCreated creates LoginMkubeCreated with default headers values
func NewLoginMkubeCreated() *LoginMkubeCreated {
return &LoginMkubeCreated{}
}
// WithPayload adds the payload to the login mkube created response
func (o *LoginMkubeCreated) WithPayload(payload *models.LoginResponse) *LoginMkubeCreated {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login mkube created response
func (o *LoginMkubeCreated) SetPayload(payload *models.LoginResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginMkubeCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(201)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*LoginMkubeDefault Generic error response.
swagger:response loginMkubeDefault
*/
type LoginMkubeDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewLoginMkubeDefault creates LoginMkubeDefault with default headers values
func NewLoginMkubeDefault(code int) *LoginMkubeDefault {
if code <= 0 {
code = 500
}
return &LoginMkubeDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the login mkube default response
func (o *LoginMkubeDefault) WithStatusCode(code int) *LoginMkubeDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the login mkube default response
func (o *LoginMkubeDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the login mkube default response
func (o *LoginMkubeDefault) WithPayload(payload *models.Error) *LoginMkubeDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the login mkube default response
func (o *LoginMkubeDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *LoginMkubeDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,104 @@
// Code generated by go-swagger; DO NOT EDIT.
// 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 user_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
)
// LoginMkubeURL generates an URL for the login mkube operation
type LoginMkubeURL struct {
_basePath string
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginMkubeURL) WithBasePath(bp string) *LoginMkubeURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *LoginMkubeURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *LoginMkubeURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/login/mkube"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *LoginMkubeURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *LoginMkubeURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *LoginMkubeURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on LoginMkubeURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on LoginMkubeURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *LoginMkubeURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -60,7 +60,7 @@ func prepareSTSClientTransport() *http.Transport {
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
}
// If root CAs are configured we save them to the http.Client RootCAs store
// If CAs certificates are configured we save them to the http.Client RootCAs store
if len(caCertFileNames) > 0 {
certs := x509.NewCertPool()
for _, caCert := range caCertFileNames {
@@ -85,7 +85,7 @@ func prepareSTSClientTransport() *http.Transport {
}
// PrepareSTSClient returns an http.Client with custom configurations need it by *credentials.STSAssumeRole
// custom configurations include skipVerification flag, and root CA certificates
// custom configurations include the use of CA certificates
func PrepareSTSClient() *http.Client {
transport := prepareSTSClientTransport()
// Return http client with default configuration

View File

@@ -23,6 +23,7 @@ import (
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/mcs/models"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/restapi/operations"
"github.com/minio/mcs/restapi/operations/user_api"
"github.com/minio/minio-go/v6"
@@ -181,7 +182,11 @@ func createBucketEvent(client MCS3Client, arn string, notificationEvents []model
// getCreateBucketEventsResponse calls createBucketEvent to add a bucket event notification
func getCreateBucketEventsResponse(sessionID, bucketName string, eventReq *models.BucketEventRequest) error {
s3Client, err := newS3BucketClient(sessionID, bucketName)
claims, err := auth.JWTAuthenticate(sessionID)
if err != nil {
return err
}
s3Client, err := newS3BucketClient(claims, bucketName)
if err != nil {
log.Println("error creating S3Client:", err)
return err
@@ -217,7 +222,11 @@ func joinNotificationEvents(events []models.NotificationEventType) string {
// getDeleteBucketEventsResponse calls deleteBucketEventNotification() to delete a bucket event notification
func getDeleteBucketEventsResponse(sessionID, bucketName string, arn string, events []models.NotificationEventType, prefix, suffix *string) error {
s3Client, err := newS3BucketClient(sessionID, bucketName)
claims, err := auth.JWTAuthenticate(sessionID)
if err != nil {
return err
}
s3Client, err := newS3BucketClient(claims, bucketName)
if err != nil {
log.Println("error creating S3Client:", err)
return err

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
}

View File

@@ -37,7 +37,7 @@ func registerLogoutHandlers(api *operations.McsAPI) {
})
}
// logout() call Expire() on the provided minioCredentials
// logout() call Expire() on the provided mcsCredentials
func logout(credentials MCSCredentials) {
credentials.Expire()
}
@@ -49,7 +49,7 @@ func getLogoutResponse(jwt string) error {
log.Println(err)
return err
}
credentials := mcsCredentials{minioCredentials: creds}
credentials := mcsCredentials{mcsCredentials: creds}
if err != nil {
log.Println("error creating MinIO Client:", err)
return err

View File

@@ -26,7 +26,6 @@ import (
"github.com/go-openapi/errors"
"github.com/gorilla/websocket"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/pkg/ws"
)
var upgrader = websocket.Upgrader{
@@ -99,14 +98,9 @@ func (c wsConn) readMessage() (messageType int, p []byte, err error) {
// on the path.
// Request should come like ws://<host>:<port>/ws/<api>
func serveWS(w http.ResponseWriter, req *http.Request) {
sessionID, err := ws.GetTokenFromRequest(req)
if err != nil {
errors.ServeError(w, req, err)
return
}
// Perform authentication before upgrading to a Websocket Connection
// authenticate WS connection with MCS
claims, err := auth.JWTAuthenticate(*sessionID)
claims, err := auth.GetClaimsFromTokenInRequest(req)
if err != nil {
log.Print("error on ws authentication: ", err)
errors.ServeError(w, req, errors.New(http.StatusUnauthorized, err.Error()))
@@ -152,7 +146,7 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
go wsAdminClient.heal(hOptions)
case strings.HasPrefix(wsPath, `/watch`):
wOptions := getWatchOptionsFromReq(req)
wsS3Client, err := newWebSocketS3Client(conn, *sessionID, wOptions.BucketName)
wsS3Client, err := newWebSocketS3Client(conn, claims, wOptions.BucketName)
if err != nil {
closeWsConn(conn)
return
@@ -188,10 +182,10 @@ func newWebSocketAdminClient(conn *websocket.Conn, autClaims *auth.DecryptedClai
}
// newWebSocketS3Client returns a wsAdminClient authenticated as MCS admin
func newWebSocketS3Client(conn *websocket.Conn, jwt, bucketName string) (*wsS3Client, error) {
func newWebSocketS3Client(conn *websocket.Conn, claims *auth.DecryptedClaims, bucketName string) (*wsS3Client, error) {
// Only start Websocket Interaction after user has been
// authenticated with MinIO
s3Client, err := newS3BucketClient(jwt, bucketName)
s3Client, err := newS3BucketClient(claims, bucketName)
if err != nil {
log.Println("error creating S3Client:", err)
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))

View File

@@ -60,6 +60,28 @@ paths:
security: []
tags:
- UserAPI
/login/mkube:
post:
summary: Login to Mkube.
operationId: LoginMkube
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/loginMkubeRequest'
responses:
201:
description: A successful login.
schema:
$ref: '#/definitions/loginResponse'
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: []
tags:
- UserAPI
/login/oauth2/auth:
post:
@@ -354,7 +376,7 @@ paths:
$ref: "#/definitions/error"
tags:
- UserAPI
/service-accounts/{access_key}:
delete:
summary: Delete Service Account
@@ -1173,7 +1195,7 @@ definitions:
- get
notificationConfig:
type: object
required:
required:
- arn
properties:
id:
@@ -1212,7 +1234,7 @@ definitions:
title: "filter event associated to the specified suffix"
bucketEventRequest:
type: object
required:
required:
- configuration
properties:
configuration:
@@ -1242,7 +1264,7 @@ definitions:
properties:
loginStrategy:
type: string
enum: [form,redirect]
enum: [form,redirect,service-account]
redirect:
type: string
loginOauth2AuthRequest:
@@ -1255,6 +1277,13 @@ definitions:
type: string
code:
type: string
loginMkubeRequest:
type: object
required:
- jwt
properties:
jwt:
type: string
loginRequest:
type: object
required: