2021-08-18 21:35:22 -04:00
|
|
|
//go:build ignore
|
2019-01-04 16:48:12 -05:00
|
|
|
// +build ignore
|
|
|
|
|
2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 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/>.
|
2019-01-04 16:48:12 -05:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-06-30 10:43:04 -04:00
|
|
|
"bytes"
|
2019-01-04 16:48:12 -05:00
|
|
|
"context"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2019-11-30 00:37:42 -05:00
|
|
|
"encoding/json"
|
2019-11-13 05:30:54 -05:00
|
|
|
"errors"
|
2019-01-04 16:48:12 -05:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2020-06-24 10:49:09 -04:00
|
|
|
"strings"
|
2019-01-04 16:48:12 -05:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
|
2020-07-14 12:38:05 -04:00
|
|
|
"github.com/minio/minio-go/v7"
|
|
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
2019-01-04 16:48:12 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Returns a base64 encoded random 32 byte string.
|
|
|
|
func randomState() string {
|
|
|
|
b := make([]byte, 32)
|
|
|
|
rand.Read(b)
|
|
|
|
return base64.RawURLEncoding.EncodeToString(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2019-11-30 00:37:42 -05:00
|
|
|
stsEndpoint string
|
|
|
|
configEndpoint string
|
|
|
|
clientID string
|
|
|
|
clientSec string
|
2020-06-24 10:49:09 -04:00
|
|
|
clientScopes string
|
2019-11-30 00:37:42 -05:00
|
|
|
port int
|
2019-01-04 16:48:12 -05:00
|
|
|
)
|
|
|
|
|
2019-11-30 00:37:42 -05:00
|
|
|
// DiscoveryDoc - parses the output from openid-configuration
|
2020-07-15 07:55:55 -04:00
|
|
|
// for example http://localhost:8080/auth/realms/minio/.well-known/openid-configuration
|
2019-11-30 00:37:42 -05:00
|
|
|
type DiscoveryDoc struct {
|
|
|
|
Issuer string `json:"issuer,omitempty"`
|
|
|
|
AuthEndpoint string `json:"authorization_endpoint,omitempty"`
|
|
|
|
TokenEndpoint string `json:"token_endpoint,omitempty"`
|
|
|
|
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
|
|
|
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
|
|
|
JwksURI string `json:"jwks_uri,omitempty"`
|
|
|
|
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
|
|
|
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
|
|
|
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
|
|
|
|
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
|
|
|
TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
|
|
|
|
ClaimsSupported []string `json:"claims_supported,omitempty"`
|
|
|
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseDiscoveryDoc(ustr string) (DiscoveryDoc, error) {
|
|
|
|
d := DiscoveryDoc{}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, ustr, nil)
|
|
|
|
if err != nil {
|
|
|
|
return d, err
|
|
|
|
}
|
|
|
|
clnt := http.Client{
|
|
|
|
Transport: http.DefaultTransport,
|
|
|
|
}
|
|
|
|
resp, err := clnt.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return d, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
2023-05-19 05:13:33 -04:00
|
|
|
return d, fmt.Errorf("unexpected error returned by %s : status(%s)", ustr, resp.Status)
|
2019-11-30 00:37:42 -05:00
|
|
|
}
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
if err = dec.Decode(&d); err != nil {
|
|
|
|
return d, err
|
|
|
|
}
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
2019-01-04 16:48:12 -05:00
|
|
|
func init() {
|
|
|
|
flag.StringVar(&stsEndpoint, "sts-ep", "http://localhost:9000", "STS endpoint")
|
2019-11-30 00:37:42 -05:00
|
|
|
flag.StringVar(&configEndpoint, "config-ep",
|
2020-07-15 07:55:55 -04:00
|
|
|
"http://localhost:8080/auth/realms/minio/.well-known/openid-configuration",
|
2019-11-30 00:37:42 -05:00
|
|
|
"OpenID discovery document endpoint")
|
2019-01-04 16:48:12 -05:00
|
|
|
flag.StringVar(&clientID, "cid", "", "Client ID")
|
2019-11-30 00:37:42 -05:00
|
|
|
flag.StringVar(&clientSec, "csec", "", "Client Secret")
|
2020-06-24 10:49:09 -04:00
|
|
|
flag.StringVar(&clientScopes, "cscopes", "openid", "Client Scopes")
|
2019-10-23 01:59:13 -04:00
|
|
|
flag.IntVar(&port, "port", 8080, "Port")
|
2019-01-04 16:48:12 -05:00
|
|
|
}
|
|
|
|
|
2021-06-30 10:43:04 -04:00
|
|
|
func implicitFlowURL(c oauth2.Config, state string) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
buf.WriteString(c.Endpoint.AuthURL)
|
|
|
|
v := url.Values{
|
|
|
|
"response_type": {"id_token"},
|
|
|
|
"response_mode": {"form_post"},
|
|
|
|
"client_id": {c.ClientID},
|
|
|
|
}
|
|
|
|
if c.RedirectURL != "" {
|
|
|
|
v.Set("redirect_uri", c.RedirectURL)
|
|
|
|
}
|
|
|
|
if len(c.Scopes) > 0 {
|
|
|
|
v.Set("scope", strings.Join(c.Scopes, " "))
|
|
|
|
}
|
|
|
|
v.Set("state", state)
|
|
|
|
v.Set("nonce", state)
|
|
|
|
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
|
|
|
buf.WriteByte('&')
|
|
|
|
} else {
|
|
|
|
buf.WriteByte('?')
|
|
|
|
}
|
|
|
|
buf.WriteString(v.Encode())
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2019-01-04 16:48:12 -05:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
2021-06-30 10:43:04 -04:00
|
|
|
if clientID == "" {
|
2019-01-04 16:48:12 -05:00
|
|
|
flag.PrintDefaults()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-30 00:37:42 -05:00
|
|
|
ddoc, err := parseDiscoveryDoc(configEndpoint)
|
|
|
|
if err != nil {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Errorf("Failed to parse OIDC discovery document %s", err))
|
2019-11-30 00:37:42 -05:00
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-15 07:55:55 -04:00
|
|
|
scopes := ddoc.ScopesSupported
|
2020-06-24 10:49:09 -04:00
|
|
|
if clientScopes != "" {
|
2020-07-15 07:55:55 -04:00
|
|
|
scopes = strings.Split(clientScopes, ",")
|
2020-06-24 10:49:09 -04:00
|
|
|
}
|
|
|
|
|
2019-01-04 16:48:12 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
config := oauth2.Config{
|
|
|
|
ClientID: clientID,
|
2019-11-30 00:37:42 -05:00
|
|
|
ClientSecret: clientSec,
|
2019-01-04 16:48:12 -05:00
|
|
|
Endpoint: oauth2.Endpoint{
|
2019-11-30 00:37:42 -05:00
|
|
|
AuthURL: ddoc.AuthEndpoint,
|
|
|
|
TokenURL: ddoc.TokenEndpoint,
|
2019-01-04 16:48:12 -05:00
|
|
|
},
|
2021-06-17 23:27:04 -04:00
|
|
|
RedirectURL: fmt.Sprintf("http://10.0.0.67:%d/oauth2/callback", port),
|
2020-06-24 10:49:09 -04:00
|
|
|
Scopes: scopes,
|
2019-01-04 16:48:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
state := randomState()
|
|
|
|
|
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Printf("%s %s", r.Method, r.RequestURI)
|
|
|
|
if r.RequestURI != "/" {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2021-06-30 10:43:04 -04:00
|
|
|
if clientSec != "" {
|
|
|
|
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
|
|
|
|
} else {
|
|
|
|
http.Redirect(w, r, implicitFlowURL(config, state), http.StatusFound)
|
|
|
|
}
|
2019-01-04 16:48:12 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
http.HandleFunc("/oauth2/callback", func(w http.ResponseWriter, r *http.Request) {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Printf("%s %s", r.Method, r.RequestURI)
|
2021-06-30 10:43:04 -04:00
|
|
|
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if r.Form.Get("state") != state {
|
2019-01-04 16:48:12 -05:00
|
|
|
http.Error(w, "state did not match", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-30 10:43:04 -04:00
|
|
|
var getWebTokenExpiry func() (*credentials.WebIdentityToken, error)
|
|
|
|
if clientSec == "" {
|
|
|
|
getWebTokenExpiry = func() (*credentials.WebIdentityToken, error) {
|
|
|
|
return &credentials.WebIdentityToken{
|
|
|
|
Token: r.Form.Get("id_token"),
|
|
|
|
}, nil
|
2019-11-13 05:30:54 -05:00
|
|
|
}
|
2021-06-30 10:43:04 -04:00
|
|
|
} else {
|
|
|
|
getWebTokenExpiry = func() (*credentials.WebIdentityToken, error) {
|
|
|
|
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !oauth2Token.Valid() {
|
|
|
|
return nil, errors.New("invalid token")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &credentials.WebIdentityToken{
|
|
|
|
Token: oauth2Token.Extra("id_token").(string),
|
|
|
|
Expiry: int(oauth2Token.Expiry.Sub(time.Now().UTC()).Seconds()),
|
|
|
|
}, nil
|
2019-11-13 05:30:54 -05:00
|
|
|
}
|
|
|
|
}
|
2019-11-30 00:37:42 -05:00
|
|
|
|
2019-11-13 05:30:54 -05:00
|
|
|
sts, err := credentials.NewSTSWebIdentity(stsEndpoint, getWebTokenExpiry)
|
2019-01-04 16:48:12 -05:00
|
|
|
if err != nil {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Errorf("Could not get STS credentials: %s", err))
|
2019-11-13 05:30:54 -05:00
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
2019-01-04 16:48:12 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-13 05:30:54 -05:00
|
|
|
opts := &minio.Options{
|
|
|
|
Creds: sts,
|
|
|
|
BucketLookup: minio.BucketLookupAuto,
|
|
|
|
}
|
2019-01-04 16:48:12 -05:00
|
|
|
|
2019-11-13 05:30:54 -05:00
|
|
|
u, err := url.Parse(stsEndpoint)
|
|
|
|
if err != nil {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Errorf("Failed to parse STS Endpoint: %s", err))
|
2019-11-13 05:30:54 -05:00
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2019-01-04 16:48:12 -05:00
|
|
|
|
2020-10-24 14:16:59 -04:00
|
|
|
clnt, err := minio.New(u.Host, opts)
|
2019-11-13 05:30:54 -05:00
|
|
|
if err != nil {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Errorf("Error while initializing Minio client, %s", err))
|
2019-11-13 05:30:54 -05:00
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2020-10-24 14:16:59 -04:00
|
|
|
buckets, err := clnt.ListBuckets(r.Context())
|
2019-11-13 05:30:54 -05:00
|
|
|
if err != nil {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Errorf("Error while listing buckets, %s", err))
|
2019-11-13 05:30:54 -05:00
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2020-03-14 01:58:53 -04:00
|
|
|
creds, _ := sts.Get()
|
|
|
|
|
|
|
|
bucketNames := []string{}
|
|
|
|
|
2019-11-13 05:30:54 -05:00
|
|
|
for _, bucket := range buckets {
|
2020-03-14 01:58:53 -04:00
|
|
|
log.Println(fmt.Sprintf("Bucket discovered: %s", bucket.Name))
|
|
|
|
bucketNames = append(bucketNames, bucket.Name)
|
|
|
|
}
|
|
|
|
response := make(map[string]interface{})
|
|
|
|
response["credentials"] = creds
|
|
|
|
response["buckets"] = bucketNames
|
|
|
|
c, err := json.MarshalIndent(response, "", "\t")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
2019-01-04 16:48:12 -05:00
|
|
|
}
|
2020-03-14 01:58:53 -04:00
|
|
|
w.Write(c)
|
2019-01-04 16:48:12 -05:00
|
|
|
})
|
|
|
|
|
2021-06-17 23:27:04 -04:00
|
|
|
address := fmt.Sprintf(":%v", port)
|
2019-10-23 01:59:13 -04:00
|
|
|
log.Printf("listening on http://%s/", address)
|
|
|
|
log.Fatal(http.ListenAndServe(address, nil))
|
2019-01-04 16:48:12 -05:00
|
|
|
}
|