mirror of
https://github.com/minio/minio.git
synced 2025-11-09 13:39:46 -05:00
Add support for Identity Management Plugin (#14913)
- Adds an STS API `AssumeRoleWithCustomToken` that can be used to authenticate via the Id. Mgmt. Plugin. - Adds a sample identity manager plugin implementation - Add doc for plugin and STS API - Add an example program using go SDK for AssumeRoleWithCustomToken
This commit is contained in:
committed by
GitHub
parent
5c81d0d89a
commit
464b9d7c80
76
docs/iam/identity-management-plugin.md
Normal file
76
docs/iam/identity-management-plugin.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Identity Management Plugin Guide [](https://slack.minio.io)
|
||||
|
||||
## Introduction
|
||||
|
||||
To enable the integration of custom authentication methods, MinIO can be configured with an Identity Management Plugin webhook. When configured, this plugin enables the `AssumeRoleWithCustomToken` STS API extension. A user or application can now present a token to the `AssumeRoleWithCustomToken` API, and MinIO verifies this token by sending it to the Identity Management Plugin webhook. This plugin responds with some information and MinIO is able to generate temporary STS credentials to interact with object storage.
|
||||
|
||||
The authentication flow is similar to that of OpenID, however the token is "opaque" to MinIO - it is simply sent to the plugin for verification. CAVEAT: There is no console UI integration for this method of authentication and it is intended primarily for machine authentication.
|
||||
|
||||
It can be configured via MinIO's standard configuration API (i.e. using `mc admin config set/get`), or equivalently with environment variables. For brevity we show only environment variables here:
|
||||
|
||||
```sh
|
||||
$ mc admin config set myminio identity_plugin --env
|
||||
KEY:
|
||||
identity_plugin enable Identity Plugin via external hook
|
||||
|
||||
ARGS:
|
||||
MINIO_IDENTITY_PLUGIN_URL* (url) plugin hook endpoint (HTTP(S)) e.g. "http://localhost:8181/path/to/endpoint"
|
||||
MINIO_IDENTITY_PLUGIN_AUTH_TOKEN (string) authorization token for plugin hook endpoint
|
||||
MINIO_IDENTITY_PLUGIN_ROLE_POLICY* (string) policies to apply for plugin authorized users
|
||||
MINIO_IDENTITY_PLUGIN_ROLE_ID (string) unique ID to generate the ARN
|
||||
MINIO_IDENTITY_PLUGIN_COMMENT (sentence) optionally add a comment to this setting
|
||||
```
|
||||
|
||||
If provided, the auth token parameter is sent as an authorization header.
|
||||
|
||||
`MINIO_IDENTITY_PLUGIN_ROLE_POLICY` is a required parameter and can be list of comma separated policy names.
|
||||
|
||||
On setting up the plugin, the MinIO server prints the Role ARN to its log. The Role ARN is generated by default based on the given plugin URL. To avoid this and use a configurable value set a unique role ID via `MINIO_IDENTITY_PLUGIN_ROLE_ID`.
|
||||
|
||||
## REST API call to plugin
|
||||
|
||||
To verify the custom token presented in the `AssumeRoleWithCustomToken` API, MinIO makes a POST request to the configured identity management plugin endpoint and expects a response with some details as shown below:
|
||||
|
||||
### Request `POST` to plugin endpoint
|
||||
|
||||
Query parameters:
|
||||
|
||||
| Parameter Name | Value Type | Purpose |
|
||||
|----------------|------------|-------------------------------------------------------------------------|
|
||||
| token | string | Token from the AssumeRoleWithCustomToken call for external verification |
|
||||
|
||||
### Response
|
||||
|
||||
If the token is valid and access is approved, the plugin must return a `200` (OK) HTTP status code.
|
||||
|
||||
A `200 OK` Response should have `application/json` content-type and body with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": <string>,
|
||||
"maxValiditySeconds": <integer>,
|
||||
"claims": <key-value-pairs>
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter Name | Value Type | Purpose |
|
||||
|--------------------|-----------------------------------------|--------------------------------------------------------|
|
||||
| user | string | Identifier for owner of requested credentials |
|
||||
| maxValiditySeconds | integer (>= 900 seconds and < 365 days) | Maximum allowed expiry duration for the credentials |
|
||||
| claims | key-value pairs | Claims to be associated with the requested credentials |
|
||||
|
||||
The keys "exp", "parent" and "sub" in the `claims` object are reserved and if present are ignored by MinIO.
|
||||
|
||||
If the token is not valid or access is not approved, the plugin must return a `403` (forbidden) HTTP status code. The body must have an `application/json` content-type with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"reason": <string>
|
||||
}
|
||||
```
|
||||
|
||||
The reason message is returned to the client.
|
||||
|
||||
## Example Plugin Implementation
|
||||
|
||||
A toy example for the Identity Management Plugin is given [here](./identity-manager-plugin.go).
|
||||
86
docs/iam/identity-manager-plugin.go
Normal file
86
docs/iam/identity-manager-plugin.go
Normal file
@@ -0,0 +1,86 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func writeErrorResponse(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"reason": fmt.Sprintf("%v", err),
|
||||
})
|
||||
}
|
||||
|
||||
type Resp struct {
|
||||
User string `json:"user"`
|
||||
MaxValiditySeconds int `json:"maxValiditySeconds"`
|
||||
Claims map[string]interface{} `json:"claims"`
|
||||
}
|
||||
|
||||
var tokens map[string]Resp = map[string]Resp{
|
||||
"aaa": {
|
||||
User: "Alice",
|
||||
MaxValiditySeconds: 3600,
|
||||
Claims: map[string]interface{}{
|
||||
"groups": []string{"data-science"},
|
||||
},
|
||||
},
|
||||
"bbb": {
|
||||
User: "Bart",
|
||||
MaxValiditySeconds: 3600,
|
||||
Claims: map[string]interface{}{
|
||||
"groups": []string{"databases"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func mainHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.FormValue("token")
|
||||
if token == "" {
|
||||
writeErrorResponse(w, errors.New("token parameter not given"))
|
||||
return
|
||||
}
|
||||
|
||||
rsp, ok := tokens[token]
|
||||
if !ok {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Allowed for token: %s user: %s\n", token, rsp.User)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(rsp)
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", mainHandler)
|
||||
|
||||
log.Print("Listing on :8081")
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}
|
||||
121
docs/sts/custom-token-identity.go
Normal file
121
docs/sts/custom-token-identity.go
Normal file
@@ -0,0 +1,121 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
// LDAP integrated Minio endpoint
|
||||
stsEndpoint string
|
||||
|
||||
// token to use with AssumeRoleWithCustomToken
|
||||
token string
|
||||
|
||||
// Role ARN to use
|
||||
roleArn string
|
||||
|
||||
// Display credentials flag
|
||||
displayCreds bool
|
||||
|
||||
// Credential expiry duration
|
||||
expiryDuration time.Duration
|
||||
|
||||
// Bucket to list
|
||||
bucketToList string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&stsEndpoint, "sts-ep", "http://localhost:9000", "STS endpoint")
|
||||
flag.StringVar(&token, "t", "", "Token to use with AssumeRoleWithCustomToken STS API (required)")
|
||||
flag.StringVar(&roleArn, "r", "", "RoleARN to use with the request (required)")
|
||||
flag.BoolVar(&displayCreds, "d", false, "Only show generated credentials")
|
||||
flag.DurationVar(&expiryDuration, "e", 0, "Request a duration of validity for the generated credential")
|
||||
flag.StringVar(&bucketToList, "b", "mybucket", "Bucket to list (defaults to mybucket)")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if token == "" || roleArn == "" {
|
||||
flag.PrintDefaults()
|
||||
return
|
||||
}
|
||||
|
||||
// The credentials package in minio-go provides an interface to call the
|
||||
// AssumeRoleWithCustomToken STS API.
|
||||
|
||||
var opts []cr.CustomTokenOpt
|
||||
if expiryDuration != 0 {
|
||||
opts = append(opts, cr.CustomTokenValidityOpt(expiryDuration))
|
||||
}
|
||||
|
||||
// Initialize
|
||||
li, err := cr.NewCustomTokenCredentials(stsEndpoint, token, roleArn, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing CustomToken Identity: %v", err)
|
||||
}
|
||||
|
||||
v, err := li.Get()
|
||||
if err != nil {
|
||||
log.Fatalf("Error retrieving STS credentials: %v", err)
|
||||
}
|
||||
|
||||
if displayCreds {
|
||||
fmt.Println("Only displaying credentials:")
|
||||
fmt.Println("AccessKeyID:", v.AccessKeyID)
|
||||
fmt.Println("SecretAccessKey:", v.SecretAccessKey)
|
||||
fmt.Println("SessionToken:", v.SessionToken)
|
||||
return
|
||||
}
|
||||
|
||||
// Use generated credentials to authenticate with MinIO server
|
||||
stsEndpointURL, err := url.Parse(stsEndpoint)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing sts endpoint: %v", err)
|
||||
}
|
||||
copts := &minio.Options{
|
||||
Creds: li,
|
||||
Secure: stsEndpointURL.Scheme == "https",
|
||||
}
|
||||
minioClient, err := minio.New(stsEndpointURL.Host, copts)
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing client: ", err)
|
||||
}
|
||||
|
||||
// Use minIO Client object normally like the regular client.
|
||||
fmt.Printf("Calling list objects on bucket named `%s` with temp creds:\n===\n", bucketToList)
|
||||
objCh := minioClient.ListObjects(context.Background(), bucketToList, minio.ListObjectsOptions{})
|
||||
for obj := range objCh {
|
||||
if obj.Err != nil {
|
||||
log.Fatalf("Listing error: %v", obj.Err)
|
||||
}
|
||||
fmt.Printf("Key: %s\nSize: %d\nLast Modified: %s\n===\n", obj.Key, obj.Size, obj.LastModified)
|
||||
}
|
||||
}
|
||||
53
docs/sts/custom-token-identity.md
Normal file
53
docs/sts/custom-token-identity.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# AssumeRoleWithCustomToken [](https://slack.min.io)
|
||||
|
||||
## Introduction
|
||||
|
||||
To integrate with custom authentication methods using the [Identity Management Plugin](../iam/identity-management-plugin.md)), MinIO provides an STS API extension called `AssumeRoleWithCustomToken`.
|
||||
|
||||
After configuring the plugin, use the generated Role ARN with `AssumeRoleWithCustomToken` to get temporary credentials to access object storage.
|
||||
|
||||
## API Request
|
||||
|
||||
To make an STS API request with this method, send a POST request to the MinIO endpoint with following query parameters:
|
||||
|
||||
| Parameter | Type | Required | |
|
||||
|-----------------|---------|----------|----------------------------------------------------------------------|
|
||||
| Action | String | Yes | Value must be `AssumeRoleWithCustomToken` |
|
||||
| Version | String | Yes | Value must be `2011-06-15` |
|
||||
| Token | String | Yes | Token to be authenticated by identity plugin |
|
||||
| RoleArn | String | Yes | Must match the Role ARN generated for the identity plugin |
|
||||
| DurationSeconds | Integer | No | Duration of validity of generated credentials. Must be at least 900. |
|
||||
|
||||
The validity duration of the generated STS credentials is the minimum of the `DurationSeconds` parameter (if passed) and the validity duration returned by the Identity Management Plugin.
|
||||
|
||||
## API Response
|
||||
|
||||
XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements)
|
||||
|
||||
## Example request and response
|
||||
|
||||
Sample request with `curl`:
|
||||
|
||||
```sh
|
||||
curl -XPOST 'http://localhost:9001/?Action=AssumeRoleWithCustomToken&Version=2011-06-15&Token=aaa&RoleArn=arn:minio:iam:::role/idmp-vGxBdLkOc8mQPU1-UQbBh-yWWVQ'
|
||||
```
|
||||
|
||||
Prettified Response:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<AssumeRoleWithCustomTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
||||
<AssumeRoleWithCustomTokenResult>
|
||||
<Credentials>
|
||||
<AccessKeyId>24Y5H9VHE14H47GEOKCX</AccessKeyId>
|
||||
<SecretAccessKey>H+aBfQ9B1AeWWb++84hvp4tlFBo9aP+hUTdLFIeg</SecretAccessKey>
|
||||
<Expiration>2022-05-25T19:56:34Z</Expiration>
|
||||
<SessionToken>eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiIyNFk1SDlWSEUxNEg0N0dFT0tDWCIsImV4cCI6MTY1MzUwODU5NCwiZ3JvdXBzIjpbImRhdGEtc2NpZW5jZSJdLCJwYXJlbnQiOiJjdXN0b206QWxpY2UiLCJyb2xlQXJuIjoiYXJuOm1pbmlvOmlhbTo6OnJvbGUvaWRtcC14eHgiLCJzdWIiOiJjdXN0b206QWxpY2UifQ.1tO1LmlUNXiy-wl-ZbkJLWTpaPlhaGqHehsi21lNAmAGCImHHsPb-GA4lRq6GkvHAODN5ZYCf_S-OwpOOdxFwA</SessionToken>
|
||||
</Credentials>
|
||||
<AssumedUser>custom:Alice</AssumedUser>
|
||||
</AssumeRoleWithCustomTokenResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>16F26E081E36DE63</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AssumeRoleWithCustomTokenResponse>
|
||||
```
|
||||
Reference in New Issue
Block a user