github auth: use org id to verify creds (#13332)

* github auth: use org id to verify creds

* add check for required org param; add test case

* update UTs

* add nil check for org

* add changelog

* fix typo in ut

* set org ID if it is unset; add more ut coverage

* add optional organization_id

* move client instantiation

* refactor parse URL; add UT for setting org ID

* fix comment in UT

* add nil check

* don't update org name on change; return warning

* refactor verifyCredentials

* error when unable to fetch org ID on config write; add warnings

* fix bug in log message

* update UT and small refactor

* update comments and log msg

* use getter for org ID
This commit is contained in:
John-Michael Faircloth
2021-12-14 16:37:19 -06:00
committed by GitHub
parent 9b86b8cf90
commit 524ded982b
6 changed files with 544 additions and 54 deletions

View File

@@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/google/go-github/github"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
@@ -19,8 +20,12 @@ func pathConfig(b *backend) *framework.Path {
"organization": {
Type: framework.TypeString,
Description: "The organization users must be part of",
Required: true,
},
"organization_id": {
Type: framework.TypeInt64,
Description: "The ID of the organization users must be part of",
},
"base_url": {
Type: framework.TypeString,
Description: `The API endpoint to use. Useful if you
@@ -55,6 +60,7 @@ API-compatible authentication server.`,
}
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var resp logical.Response
c, err := b.Config(ctx, req.Storage)
if err != nil {
return nil, err
@@ -66,19 +72,47 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat
if organizationRaw, ok := data.GetOk("organization"); ok {
c.Organization = organizationRaw.(string)
}
if c.Organization == "" {
return logical.ErrorResponse("organization is a required parameter"), nil
}
if organizationRaw, ok := data.GetOk("organization_id"); ok {
c.OrganizationID = organizationRaw.(int64)
}
var parsedURL *url.URL
if baseURLRaw, ok := data.GetOk("base_url"); ok {
baseURL := baseURLRaw.(string)
_, err := url.Parse(baseURL)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Error parsing given base_url: %s", err)), nil
}
if !strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
parsedURL, err = url.Parse(baseURL)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("error parsing given base_url: %s", err)), nil
}
c.BaseURL = baseURL
}
if c.OrganizationID == 0 {
client, err := b.Client("")
if err != nil {
return nil, err
}
// ensure our client has the BaseURL if it was provided
if parsedURL != nil {
client.BaseURL = parsedURL
}
// we want to set the Org ID in the config so we can use that to verify
// the credentials on login
err = c.setOrganizationID(ctx, client)
if err != nil {
errorMsg := fmt.Errorf("unable to fetch the organization_id, you must manually set it in the config: %s", err)
b.Logger().Error(errorMsg.Error())
return nil, errorMsg
}
}
if err := c.ParseTokenFields(req, data); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
@@ -103,7 +137,11 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat
return nil, err
}
return nil, nil
if len(resp.Warnings) == 0 {
return nil, nil
}
return &resp, nil
}
func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
@@ -116,8 +154,9 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data
}
d := map[string]interface{}{
"organization": config.Organization,
"base_url": config.BaseURL,
"organization_id": config.OrganizationID,
"organization": config.Organization,
"base_url": config.BaseURL,
}
config.PopulateTokenData(d)
@@ -163,8 +202,25 @@ func (b *backend) Config(ctx context.Context, s logical.Storage) (*config, error
type config struct {
tokenutil.TokenParams
Organization string `json:"organization" structs:"organization" mapstructure:"organization"`
BaseURL string `json:"base_url" structs:"base_url" mapstructure:"base_url"`
TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`
MaxTTL time.Duration `json:"max_ttl" structs:"max_ttl" mapstructure:"max_ttl"`
OrganizationID int64 `json:"organization_id" structs:"organization_id" mapstructure:"organization_id"`
Organization string `json:"organization" structs:"organization" mapstructure:"organization"`
BaseURL string `json:"base_url" structs:"base_url" mapstructure:"base_url"`
TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`
MaxTTL time.Duration `json:"max_ttl" structs:"max_ttl" mapstructure:"max_ttl"`
}
func (c *config) setOrganizationID(ctx context.Context, client *github.Client) error {
org, _, err := client.Organizations.Get(ctx, c.Organization)
if err != nil {
return err
}
orgID := org.GetID()
if orgID == 0 {
return fmt.Errorf("organization_id not found for %s", c.Organization)
}
c.OrganizationID = orgID
return nil
}