TLS with user provided certificates and KES support for MinIO (#213)
This PR adds the following features:
- Allow user to provide its own keypair certificates for enable TLS in
MinIO
- Allow user to configure data encryption at rest in MinIO with KES
- Removes JWT schema for login and instead Console authentication will use
encrypted session tokens
Enable TLS between client and MinIO with user provided certificates
Instead of using AutoCert feature now the user can provide `cert` and
`key` via `tls` object, values must be valid `x509.Certificate`
formatted files encoded in `base64`
Enable encryption at rest configuring KES
User can deploy KES via Console/Operator by defining the encryption
object, AutoCert must be enabled or custom certificates for KES must be
provided, KES support 3 KMS backends: `Vault`, `AWS KMS` and `Gemalto`,
previous configuration of the KMS is necessary.
eg of body request for create-tenant
```
{
"name": "honeywell",
"access_key": "minio",
"secret_key": "minio123",
"enable_mcs": false,
"enable_ssl": false,
"service_name": "honeywell",
"zones": [
{
"name": "honeywell-zone-1",
"servers": 1,
"volumes_per_server": 4,
"volume_configuration": {
"size": 256000000,
"storage_class": "vsan-default-storage-policy"
}
}
],
"namespace": "default",
"tls": {
"tls.crt": "",
"tls.key": ""
},
"encryption": {
"server": {
"tls.crt": "",
"tls.key": ""
},
"client": {
"tls.crt": "",
"tls.key": ""
},
"vault": {
"endpoint": "http://vault:8200",
"prefix": "",
"approle": {
"id": "",
"secret": ""
}
}
}
}
```
This commit is contained in:
@@ -1,281 +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 jwt
|
||||
|
||||
// This file is a re-implementation of the original code here with some
|
||||
// additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1
|
||||
// original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go
|
||||
// borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
const (
|
||||
claimData = "data"
|
||||
claimSub = "sub"
|
||||
)
|
||||
|
||||
// SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods
|
||||
// Expects key type of []byte for both signing and validation
|
||||
type SigningMethodHMAC struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
// Specific instances for HS256, HS384, HS512
|
||||
var (
|
||||
SigningMethodHS256 *SigningMethodHMAC
|
||||
SigningMethodHS384 *SigningMethodHMAC
|
||||
SigningMethodHS512 *SigningMethodHMAC
|
||||
)
|
||||
|
||||
var (
|
||||
base64BufPool sync.Pool
|
||||
hmacSigners []*SigningMethodHMAC
|
||||
)
|
||||
|
||||
func init() {
|
||||
base64BufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, 8192)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
hmacSigners = []*SigningMethodHMAC{
|
||||
{"HS256", crypto.SHA256},
|
||||
{"HS384", crypto.SHA384},
|
||||
{"HS512", crypto.SHA512},
|
||||
}
|
||||
}
|
||||
|
||||
// StandardClaims are basically standard claims with "Data"
|
||||
type StandardClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
jwtgo.StandardClaims
|
||||
}
|
||||
|
||||
// MapClaims - implements custom unmarshaller
|
||||
type MapClaims struct {
|
||||
Data string `json:"data,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
jwtgo.MapClaims
|
||||
}
|
||||
|
||||
// NewStandardClaims - initializes standard claims
|
||||
func NewStandardClaims() *StandardClaims {
|
||||
return &StandardClaims{}
|
||||
}
|
||||
|
||||
// SetIssuer sets issuer for these claims
|
||||
func (c *StandardClaims) SetIssuer(issuer string) {
|
||||
c.Issuer = issuer
|
||||
}
|
||||
|
||||
// SetAudience sets audience for these claims
|
||||
func (c *StandardClaims) SetAudience(aud string) {
|
||||
c.Audience = aud
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *StandardClaims) SetExpiry(t time.Time) {
|
||||
c.ExpiresAt = t.Unix()
|
||||
}
|
||||
|
||||
// SetSubject sets unique identifier for the jwt
|
||||
func (c *StandardClaims) SetSubject(subject string) {
|
||||
c.Subject = subject
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *StandardClaims) SetData(data string) {
|
||||
c.Data = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *StandardClaims) Valid() error {
|
||||
if err := c.StandardClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/sub",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMapClaims - Initializes a new map claims
|
||||
func NewMapClaims() *MapClaims {
|
||||
return &MapClaims{MapClaims: jwtgo.MapClaims{}}
|
||||
}
|
||||
|
||||
// Lookup returns the value and if the key is found.
|
||||
func (c *MapClaims) Lookup(key string) (value string, ok bool) {
|
||||
var vinterface interface{}
|
||||
vinterface, ok = c.MapClaims[key]
|
||||
if ok {
|
||||
value, ok = vinterface.(string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetExpiry sets expiry in unix epoch secs
|
||||
func (c *MapClaims) SetExpiry(t time.Time) {
|
||||
c.MapClaims["exp"] = t.Unix()
|
||||
}
|
||||
|
||||
// SetData sets the "Data" custom field.
|
||||
func (c *MapClaims) SetData(data string) {
|
||||
c.MapClaims[claimData] = data
|
||||
}
|
||||
|
||||
// Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible
|
||||
// claims interface, additionally validates "Data" field.
|
||||
func (c *MapClaims) Valid() error {
|
||||
if err := c.MapClaims.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Data == "" || c.Subject == "" {
|
||||
return jwtgo.NewValidationError("data/subject",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map returns underlying low-level map claims.
|
||||
func (c *MapClaims) Map() map[string]interface{} {
|
||||
return c.MapClaims
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the MapClaims struct
|
||||
func (c *MapClaims) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.MapClaims)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7519#page-11
|
||||
type jwtHeader struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ"`
|
||||
}
|
||||
|
||||
// ParseWithClaims - parse the token string, valid methods.
|
||||
func ParseWithClaims(tokenStr string, claims *MapClaims) error {
|
||||
bufp := base64BufPool.Get().(*[]byte)
|
||||
defer base64BufPool.Put(bufp)
|
||||
|
||||
signer, err := parseUnverifiedMapClaims(tokenStr, claims, *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := strings.LastIndex(tokenStr, ".")
|
||||
if i < 0 {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
n, err := base64Decode(tokenStr[i+1:], *bufp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
claims.Data, ok = claims.Lookup(claimData)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("data missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
claims.Subject, ok = claims.Lookup(claimSub)
|
||||
if !ok {
|
||||
return jwtgo.NewValidationError("sub missing",
|
||||
jwtgo.ValidationErrorClaimsInvalid)
|
||||
}
|
||||
|
||||
hasher := hmac.New(signer.Hash.New, []byte(GetHmacJWTSecret()))
|
||||
hasher.Write([]byte(tokenStr[:i]))
|
||||
if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) {
|
||||
return jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// Signature is valid, lets validate the claims for
|
||||
// other fields such as expiry etc.
|
||||
return claims.Valid()
|
||||
}
|
||||
|
||||
// base64Decode returns the bytes represented by the base64 string s.
|
||||
func base64Decode(s string, buf []byte) (int, error) {
|
||||
return base64.RawURLEncoding.Decode(buf, []byte(s))
|
||||
}
|
||||
|
||||
// ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing
|
||||
//
|
||||
// This method parses the token but doesn't validate the signature. It's only
|
||||
// ever useful in cases where you know the signature is valid (because it has
|
||||
// been checked previously in the stack) and you want to extract values from
|
||||
// it.
|
||||
func parseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) {
|
||||
if strings.Count(tokenString, ".") != 2 {
|
||||
return nil, jwtgo.ErrSignatureInvalid
|
||||
}
|
||||
|
||||
i := strings.Index(tokenString, ".")
|
||||
j := strings.LastIndex(tokenString, ".")
|
||||
|
||||
n, err := base64Decode(tokenString[:i], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
var header = jwtHeader{}
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err = json.Unmarshal(buf[:n], &header); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
n, err = base64Decode(tokenString[i+1:j], buf)
|
||||
if err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil {
|
||||
return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
for _, signer := range hmacSigners {
|
||||
if header.Algorithm == signer.Name {
|
||||
return signer, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm),
|
||||
jwtgo.ValidationErrorUnverifiable)
|
||||
}
|
||||
@@ -24,11 +24,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCredentials = errors.New("invalid Credentials")
|
||||
errInvalidCredentials = errors.New("invalid Login")
|
||||
)
|
||||
|
||||
// GetConsoleCredentialsFromLDAP authenticates the user against MinIO when the LDAP integration is enabled
|
||||
// if the authentication succeed *credentials.Credentials object is returned and we continue with the normal STSAssumeRole flow
|
||||
// if the authentication succeed *credentials.Login object is returned and we continue with the normal STSAssumeRole flow
|
||||
func GetConsoleCredentialsFromLDAP(endpoint, ldapUser, ldapPassword string) (*credentials.Credentials, error) {
|
||||
creds, err := credentials.NewLDAPIdentity(endpoint, ldapUser, ldapPassword)
|
||||
if err != nil {
|
||||
|
||||
@@ -76,7 +76,7 @@ func isServiceAccountTokenValid(ctx context.Context, operatorClient OperatorClie
|
||||
return true
|
||||
}
|
||||
|
||||
// GetConsoleCredentialsForOperator will validate the provided JWT (service account token) and return it in the form of credentials.Credentials
|
||||
// GetConsoleCredentialsForOperator will validate the provided JWT (service account token) and return it in the form of credentials.Login
|
||||
func GetConsoleCredentialsForOperator(jwt string) (*credentials.Credentials, error) {
|
||||
ctx := context.Background()
|
||||
opClientClientSet, err := cluster.OperatorClient(jwt)
|
||||
|
||||
@@ -28,30 +28,26 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/minio/console/models"
|
||||
xjwt "github.com/minio/console/pkg/auth/jwt"
|
||||
"github.com/minio/console/pkg/auth/token"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
var (
|
||||
errAuthentication = errors.New("authentication failed, check your access credentials")
|
||||
errNoAuthToken = errors.New("JWT token missing")
|
||||
errReadingToken = errors.New("JWT internal data is malformed")
|
||||
errClaimsFormat = errors.New("encrypted jwt claims not in the right format")
|
||||
errNoAuthToken = errors.New("session token missing")
|
||||
errReadingToken = errors.New("session token internal data is malformed")
|
||||
errClaimsFormat = errors.New("encrypted session token claims not in the right format")
|
||||
)
|
||||
|
||||
// derivedKey is the key used to encrypt the JWT claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
|
||||
var derivedKey = pbkdf2.Key([]byte(xjwt.GetPBKDFPassphrase()), []byte(xjwt.GetPBKDFSalt()), 4096, 32, sha1.New)
|
||||
// derivedKey is the key used to encrypt the session token claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
|
||||
var derivedKey = pbkdf2.Key([]byte(token.GetPBKDFPassphrase()), []byte(token.GetPBKDFSalt()), 4096, 32, sha1.New)
|
||||
|
||||
// IsJWTValid returns true or false depending if the provided jwt is valid or not
|
||||
func IsJWTValid(token string) bool {
|
||||
_, err := JWTAuthenticate(token)
|
||||
// IsSessionTokenValid returns true or false depending if the provided session token is valid or not
|
||||
func IsSessionTokenValid(token string) bool {
|
||||
_, err := SessionTokenAuthenticate(token)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -63,8 +59,8 @@ type DecryptedClaims struct {
|
||||
Actions []string
|
||||
}
|
||||
|
||||
// JWTAuthenticate takes a jwt, decode it, extract claims and validate the signature
|
||||
// if the jwt claims.Data is valid we proceed to decrypt the information inside
|
||||
// SessionTokenAuthenticate takes a session token, decode it, extract claims and validate the signature
|
||||
// if the session token claims are valid we proceed to decrypt the information inside
|
||||
//
|
||||
// returns claims after validation in the following format:
|
||||
//
|
||||
@@ -73,48 +69,36 @@ type DecryptedClaims struct {
|
||||
// SecretAccessKey
|
||||
// SessionToken
|
||||
// }
|
||||
func JWTAuthenticate(token string) (*DecryptedClaims, error) {
|
||||
func SessionTokenAuthenticate(token string) (*DecryptedClaims, error) {
|
||||
if token == "" {
|
||||
return nil, errNoAuthToken
|
||||
}
|
||||
// initialize claims object
|
||||
claims := xjwt.NewMapClaims()
|
||||
// populate the claims object
|
||||
if err := xjwt.ParseWithClaims(token, claims); err != nil {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
// decrypt the claims.Data field
|
||||
claimTokens, err := decryptClaims(claims.Data)
|
||||
// decrypt encrypted token
|
||||
claimTokens, err := decryptClaims(token)
|
||||
if err != nil {
|
||||
// we print decryption token error information for debugging purposes
|
||||
log.Println(err)
|
||||
// we return a generic error that doesn't give any information to attackers
|
||||
return nil, errReadingToken
|
||||
}
|
||||
// claimsTokens contains the decrypted STS claims
|
||||
// claimsTokens contains the decrypted JWT for Console
|
||||
return claimTokens, nil
|
||||
}
|
||||
|
||||
// NewJWTWithClaimsForClient generates a new jwt with claims based on the provided STS credentials, first
|
||||
// NewEncryptedTokenForClient generates a new session token with claims based on the provided STS credentials, first
|
||||
// encrypts the claims and the sign them
|
||||
func NewJWTWithClaimsForClient(credentials *credentials.Value, actions []string, audience string) (string, error) {
|
||||
func NewEncryptedTokenForClient(credentials *credentials.Value, actions []string) (string, error) {
|
||||
if credentials != nil {
|
||||
encryptedClaims, err := encryptClaims(credentials.AccessKeyID, credentials.SecretAccessKey, credentials.SessionToken, actions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims := xjwt.NewStandardClaims()
|
||||
claims.SetExpiry(time.Now().UTC().Add(xjwt.GetConsoleSTSAndJWTDurationTime()))
|
||||
claims.SetSubject(uuid.NewV4().String())
|
||||
claims.SetData(encryptedClaims)
|
||||
claims.SetAudience(audience)
|
||||
jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, claims)
|
||||
return jwt.SignedString([]byte(xjwt.GetHmacJWTSecret()))
|
||||
return encryptedClaims, nil
|
||||
}
|
||||
return "", errors.New("provided credentials are empty")
|
||||
}
|
||||
|
||||
// encryptClaims() receives the 3 STS claims, concatenate them and encrypt them using AES-GCM
|
||||
// encryptClaims() receives the STS claims, concatenate them and encrypt them using AES-GCM
|
||||
// returns a base64 encoded ciphertext
|
||||
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string, actions []string) (string, error) {
|
||||
payload := []byte(fmt.Sprintf("%s#%s#%s#%s", accessKeyID, secretAccessKey, sessionToken, strings.Join(actions, ",")))
|
||||
@@ -189,7 +173,7 @@ func decrypt(data []byte) ([]byte, error) {
|
||||
// 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>"
|
||||
// Authorization Header needs to be like "Authorization Bearer <token>"
|
||||
func GetTokenFromRequest(r *http.Request) (*string, error) {
|
||||
// Get Auth token
|
||||
var reqToken string
|
||||
@@ -216,9 +200,9 @@ func GetClaimsFromTokenInRequest(req *http.Request) (*models.Principal, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Perform decryption of the JWT, if Console is able to decrypt the JWT that means a valid session
|
||||
// Perform decryption of the session token, if Console is able to decrypt the session token that means a valid session
|
||||
// was used in the first place to get it
|
||||
claims, err := JWTAuthenticate(*sessionID)
|
||||
claims, err := SessionTokenAuthenticate(*sessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -14,24 +14,15 @@
|
||||
// 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 jwt
|
||||
package token
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/console/pkg/auth/utils"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
// defaultHmacJWTPassphrase will be used by default if application is not configured with a custom CONSOLE_HMAC_JWT_SECRET secret
|
||||
var defaultHmacJWTPassphrase = utils.RandomCharString(64)
|
||||
|
||||
// GetHmacJWTSecret returns the 64 bytes secret used for signing the generated JWT for the application
|
||||
func GetHmacJWTSecret() string {
|
||||
return env.Get(ConsoleHmacJWTSecret, defaultHmacJWTPassphrase)
|
||||
}
|
||||
|
||||
// ConsoleSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
|
||||
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
|
||||
func GetConsoleSTSAndJWTDurationInSeconds() int {
|
||||
@@ -42,12 +33,6 @@ func GetConsoleSTSAndJWTDurationInSeconds() int {
|
||||
return duration
|
||||
}
|
||||
|
||||
// GetConsoleSTSAndJWTDurationTime returns GetConsoleSTSAndJWTDurationInSeconds in duration format
|
||||
func GetConsoleSTSAndJWTDurationTime() time.Duration {
|
||||
duration := GetConsoleSTSAndJWTDurationInSeconds()
|
||||
return time.Duration(duration) * time.Second
|
||||
}
|
||||
|
||||
var defaultPBKDFPassphrase = utils.RandomCharString(64)
|
||||
|
||||
// GetPBKDFPassphrase returns passphrase for the pbkdf2 function used to encrypt JWT payload
|
||||
@@ -14,10 +14,9 @@
|
||||
// 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 jwt
|
||||
package token
|
||||
|
||||
const (
|
||||
ConsoleHmacJWTSecret = "CONSOLE_HMAC_JWT_SECRET"
|
||||
ConsoleSTSAndJWTDurationSeconds = "CONSOLE_STS_AND_JWT_DURATION_SECONDS"
|
||||
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
|
||||
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var audience = ""
|
||||
var creds = &credentials.Value{
|
||||
AccessKeyID: "fakeAccessKeyID",
|
||||
SecretAccessKey: "fakeSecretAccessKey",
|
||||
@@ -35,25 +34,25 @@ var badToken = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiRDMwYWE0ekQ1bWt
|
||||
|
||||
func TestNewJWTWithClaimsForClient(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : NewJWTWithClaimsForClient() is generated correctly without errors
|
||||
function := "NewJWTWithClaimsForClient()"
|
||||
jwt, err := NewJWTWithClaimsForClient(creds, []string{""}, audience)
|
||||
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
|
||||
function := "NewEncryptedTokenForClient()"
|
||||
jwt, err := NewEncryptedTokenForClient(creds, []string{""})
|
||||
if err != nil || jwt == "" {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
}
|
||||
// saving jwt for future tests
|
||||
goodToken = jwt
|
||||
// Test-2 : NewJWTWithClaimsForClient() throws error because of empty credentials
|
||||
if _, err = NewJWTWithClaimsForClient(nil, []string{""}, audience); err != nil {
|
||||
// Test-2 : NewEncryptedTokenForClient() throws error because of empty credentials
|
||||
if _, err = NewEncryptedTokenForClient(nil, []string{""}); err != nil {
|
||||
funcAssert.Equal("provided credentials are empty", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() should correctly return the claims
|
||||
function := "JWTAuthenticate()"
|
||||
claims, err := JWTAuthenticate(goodToken)
|
||||
// Test-1 : SessionTokenAuthenticate() should correctly return the claims
|
||||
function := "SessionTokenAuthenticate()"
|
||||
claims, err := SessionTokenAuthenticate(goodToken)
|
||||
if err != nil || claims == nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err)
|
||||
} else {
|
||||
@@ -61,20 +60,20 @@ func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
|
||||
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
|
||||
}
|
||||
// Test-2 : JWTAuthenticate() return an error because of a tampered jwt
|
||||
if _, err := JWTAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("authentication failed, check your access credentials", err.Error())
|
||||
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered jwt
|
||||
if _, err := SessionTokenAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("session token internal data is malformed", err.Error())
|
||||
}
|
||||
// Test-3 : JWTAuthenticate() return an error because of an empty jwt
|
||||
if _, err := JWTAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("JWT token missing", err.Error())
|
||||
// Test-3 : SessionTokenAuthenticate() return an error because of an empty jwt
|
||||
if _, err := SessionTokenAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("session token missing", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJWTValid(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : JWTAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsJWTValid(goodToken))
|
||||
// Test-2 : JWTAuthenticate() provided token is invalid
|
||||
funcAssert.Equal(false, IsJWTValid(badToken))
|
||||
// Test-1 : SessionTokenAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsSessionTokenValid(goodToken))
|
||||
// Test-2 : SessionTokenAuthenticate() provided token is invalid
|
||||
funcAssert.Equal(false, IsSessionTokenValid(badToken))
|
||||
}
|
||||
144
pkg/kes/kes.go
Normal file
144
pkg/kes/kes.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package kes
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/minio/kes"
|
||||
)
|
||||
|
||||
type TLSProxyHeader struct {
|
||||
ClientCert string `yaml:"cert,omitempty"`
|
||||
}
|
||||
|
||||
type TLSProxy struct {
|
||||
Identities *[]kes.Identity `yaml:"identities,omitempty"`
|
||||
Header *TLSProxyHeader `yaml:"header,omitempty"`
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
KeyPath string `yaml:"key,omitempty"`
|
||||
CertPath string `yaml:"cert,omitempty"`
|
||||
Proxy *TLSProxy `yaml:"proxy,omitempty"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
Paths []string `yaml:"paths,omitempty"`
|
||||
Identities []kes.Identity `yaml:"identities,omitempty"`
|
||||
}
|
||||
|
||||
type Expiry struct {
|
||||
Any time.Duration `yaml:"any,omitempty"`
|
||||
Unused time.Duration `yaml:"unused,omitempty"`
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
Expiry *Expiry `yaml:"expiry,omitempty"`
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Error string `yaml:"error,omitempty"`
|
||||
Audit string `yaml:"audit,omitempty"`
|
||||
}
|
||||
|
||||
type Fs struct {
|
||||
Path string `yaml:"path,omitempty"`
|
||||
}
|
||||
|
||||
type AppRole struct {
|
||||
EnginePath string `yaml:"engine,omitempty"`
|
||||
ID string `yaml:"id,omitempty"`
|
||||
Secret string `yaml:"secret,omitempty"`
|
||||
Retry time.Duration `yaml:"retry,omitempty"`
|
||||
}
|
||||
|
||||
type VaultTLS struct {
|
||||
KeyPath string `yaml:"key,omitempty"`
|
||||
CertPath string `yaml:"cert,omitempty"`
|
||||
CAPath string `yaml:"ca,omitempty"`
|
||||
}
|
||||
|
||||
type VaultStatus struct {
|
||||
Ping time.Duration `yaml:"ping,omitempty"`
|
||||
}
|
||||
|
||||
type Vault struct {
|
||||
Endpoint string `yaml:"endpoint,omitempty"`
|
||||
EnginePath string `yaml:"engine,omitempty"`
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
Prefix string `yaml:"prefix,omitempty"`
|
||||
AppRole *AppRole `yaml:"approle,omitempty"`
|
||||
TLS *VaultTLS `yaml:"tls,omitempty"`
|
||||
Status *VaultStatus `yaml:"status,omitempty"`
|
||||
}
|
||||
|
||||
type AwsSecretManagerLogin struct {
|
||||
AccessKey string `yaml:"accesskey"`
|
||||
SecretKey string `yaml:"secretkey"`
|
||||
SessionToken string `yaml:"token"`
|
||||
}
|
||||
|
||||
type AwsSecretManager struct {
|
||||
Endpoint string `yaml:"endpoint,omitempty"`
|
||||
Region string `yaml:"region,omitempty"`
|
||||
KmsKey string ` yaml:"kmskey,omitempty"`
|
||||
Login *AwsSecretManagerLogin `yaml:"credentials,omitempty"`
|
||||
}
|
||||
|
||||
type Aws struct {
|
||||
SecretsManager *AwsSecretManager `yaml:"secretsmanager,omitempty"`
|
||||
}
|
||||
|
||||
type GemaltoCredentials struct {
|
||||
Token string `yaml:"token,omitempty"`
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
Retry time.Duration `yaml:"retry,omitempty"`
|
||||
}
|
||||
|
||||
type GemaltoTLS struct {
|
||||
CAPath string `yaml:"ca,omitempty"`
|
||||
}
|
||||
|
||||
type GemaltoKeySecure struct {
|
||||
Endpoint string `yaml:"endpoint,omitempty"`
|
||||
Credentials *GemaltoCredentials `yaml:"credentials,omitempty"`
|
||||
TLS *GemaltoTLS `yaml:"tls,omitempty"`
|
||||
}
|
||||
|
||||
type Gemalto struct {
|
||||
KeySecure *GemaltoKeySecure `yaml:"keysecure,omitempty"`
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Fs *Fs `yaml:"fs,omitempty"`
|
||||
Vault *Vault `yaml:"vault,omitempty"`
|
||||
Aws *Aws `yaml:"aws,omitempty"`
|
||||
Gemalto *Gemalto `yaml:"gemalto,omitempty"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Addr string `yaml:"address,omitempty"`
|
||||
Root kes.Identity `yaml:"root,omitempty"`
|
||||
TLS TLS `yaml:"tls,omitempty"`
|
||||
Policies map[string]Policy `yaml:"policy,omitempty"`
|
||||
Cache Cache `yaml:"cache,omitempty"`
|
||||
Log Log `yaml:"log,omitempty"`
|
||||
Keys Keys `yaml:"keys,omitempty"`
|
||||
}
|
||||
|
||||
func ParseCertificate(cert []byte) (*x509.Certificate, error) {
|
||||
for {
|
||||
var certDERBlock *pem.Block
|
||||
certDERBlock, cert = pem.Decode(cert)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
return x509.ParseCertificate(certDERBlock.Bytes)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("found no (non-CA) certificate in any PEM block")
|
||||
}
|
||||
Reference in New Issue
Block a user