azure: support deriving credentials from client id, client secret and provider url (#1300)

This commit is contained in:
Caleb Doxsey 2020-08-18 10:17:28 -06:00 committed by GitHub
parent 882b6b54ee
commit c4c8ef8e53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 13 deletions

2
cache/cache.go vendored
View file

@ -129,6 +129,8 @@ func (c *Cache) update(cfg *config.Config) error {
Provider: cfg.Options.Provider,
ProviderURL: cfg.Options.ProviderURL,
QPS: cfg.Options.QPS,
ClientID: cfg.Options.ClientID,
ClientSecret: cfg.Options.ClientSecret,
})
dataBrokerClient := databroker.NewDataBrokerServiceClient(c.localGRPCConnection)

View file

@ -632,8 +632,9 @@ func (o *Options) Validate() error {
}
// if no service account was defined, there should not be any policies that
// assert group membership
if o.ServiceAccount == "" {
// assert group membership (except for azure which can be derived from the client
// id, secret and provider url)
if o.ServiceAccount == "" && o.Provider != "azure" {
for _, p := range o.Policies {
if len(p.AllowedGroups) != 0 {
return fmt.Errorf("config: `allowed_groups` requires `idp_service_account`")

View file

@ -282,7 +282,39 @@ type ServiceAccount struct {
}
// ParseServiceAccount parses the service account in the config options.
func ParseServiceAccount(rawServiceAccount string) (*ServiceAccount, error) {
func ParseServiceAccount(options directory.Options) (*ServiceAccount, error) {
if options.ServiceAccount != "" {
return parseServiceAccountFromString(options.ServiceAccount)
}
return parseServiceAccountFromOptions(options.ClientID, options.ClientSecret, options.ProviderURL)
}
func parseServiceAccountFromOptions(clientID, clientSecret, providerURL string) (*ServiceAccount, error) {
serviceAccount := ServiceAccount{
ClientID: clientID,
ClientSecret: clientSecret,
}
var err error
serviceAccount.DirectoryID, err = parseDirectoryIDFromURL(providerURL)
if err != nil {
return nil, err
}
if serviceAccount.ClientID == "" {
return nil, fmt.Errorf("client_id is required")
}
if serviceAccount.ClientSecret == "" {
return nil, fmt.Errorf("client_secret is required")
}
if serviceAccount.DirectoryID == "" {
return nil, fmt.Errorf("directory_id is required")
}
return &serviceAccount, nil
}
func parseServiceAccountFromString(rawServiceAccount string) (*ServiceAccount, error) {
bs, err := base64.StdEncoding.DecodeString(rawServiceAccount)
if err != nil {
return nil, err
@ -306,3 +338,17 @@ func ParseServiceAccount(rawServiceAccount string) (*ServiceAccount, error) {
return &serviceAccount, nil
}
func parseDirectoryIDFromURL(providerURL string) (string, error) {
u, err := url.Parse(providerURL)
if err != nil {
return "", err
}
pathParts := strings.SplitN(u.Path, "/", 3)
if len(pathParts) != 3 {
return "", fmt.Errorf("no directory id found in path")
}
return pathParts[1], nil
}

View file

@ -2,6 +2,7 @@ package azure
import (
"context"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
@ -107,6 +108,43 @@ func Test(t *testing.T) {
}, groups)
}
func TestParseServiceAccount(t *testing.T) {
t.Run("by options", func(t *testing.T) {
serviceAccount, err := ParseServiceAccount(directory.Options{
ProviderURL: "https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0",
ClientID: "CLIENT_ID",
ClientSecret: "CLIENT_SECRET",
})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, &ServiceAccount{
ClientID: "CLIENT_ID",
ClientSecret: "CLIENT_SECRET",
DirectoryID: "0303f438-3c5c-4190-9854-08d3eb31bd9f",
}, serviceAccount)
})
t.Run("by service account", func(t *testing.T) {
serviceAccount, err := ParseServiceAccount(directory.Options{
ServiceAccount: base64.StdEncoding.EncodeToString([]byte(`{
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"directory_id": "0303f438-3c5c-4190-9854-08d3eb31bd9f"
}`)),
})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, &ServiceAccount{
ClientID: "CLIENT_ID",
ClientSecret: "CLIENT_SECRET",
DirectoryID: "0303f438-3c5c-4190-9854-08d3eb31bd9f",
}, serviceAccount)
})
}
func mustParseURL(rawurl string) *url.URL {
u, err := url.Parse(rawurl)
if err != nil {

View file

@ -24,19 +24,14 @@ type Group = directory.Group
// A User is a directory User.
type User = directory.User
// Options are the options specific to the provider.
type Options = directory.Options
// A Provider provides user group directory information.
type Provider interface {
UserGroups(ctx context.Context) ([]*Group, []*User, error)
}
// Options are the options specific to the provider.
type Options struct {
ServiceAccount string
Provider string
ProviderURL string
QPS float64
}
var globalProvider = struct {
sync.Mutex
provider Provider
@ -59,11 +54,10 @@ func GetProvider(options Options) (provider Provider) {
switch options.Provider {
case azure.Name:
serviceAccount, err := azure.ParseServiceAccount(options.ServiceAccount)
serviceAccount, err := azure.ParseServiceAccount(options)
if err == nil {
return azure.New(azure.WithServiceAccount(serviceAccount))
}
log.Warn().
Str("service", "directory").
Str("provider", options.Provider).

View file

@ -48,3 +48,13 @@ func GetUser(ctx context.Context, client databroker.DataBrokerServiceClient, use
}
return &u, nil
}
// Options are directory provider options.
type Options struct {
ServiceAccount string
Provider string
ProviderURL string
ClientID string
ClientSecret string
QPS float64
}