mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 11:07:00 +02:00
Adding support for base_url for Okta api (#3316)
* Adding support for base_url for Okta api * addressing feedback suggestions, bringing back optional group query * updating docs * cleaning up the login method * clear out production flag if base_url is set * docs updates * docs updates
This commit is contained in:
parent
4a8c33cca3
commit
3aa68c0034
@ -83,32 +83,25 @@ func (b *backend) Login(req *logical.Request, username string, password string)
|
||||
return nil, logical.ErrorResponse("okta auth backend unexpected failure"), nil
|
||||
}
|
||||
|
||||
oktaUser := &result.Embedded.User
|
||||
rsp, err = client.Users.PopulateGroups(oktaUser)
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, logical.ErrorResponse("okta auth backend unexpected failure"), nil
|
||||
}
|
||||
oktaGroups := make([]string, 0, len(oktaUser.Groups))
|
||||
for _, group := range oktaUser.Groups {
|
||||
oktaGroups = append(oktaGroups, group.Profile.Name)
|
||||
}
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug("auth/okta: Groups fetched from Okta", "num_groups", len(oktaGroups), "groups", oktaGroups)
|
||||
}
|
||||
|
||||
oktaResponse := &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
var allGroups []string
|
||||
// Only query the Okta API for group membership if we have a token
|
||||
if cfg.Token != "" {
|
||||
oktaGroups, err := b.getOktaGroups(client, &result.Embedded.User)
|
||||
if err != nil {
|
||||
return nil, logical.ErrorResponse(fmt.Sprintf("okta failure retrieving groups: %v", err)), nil
|
||||
}
|
||||
if len(oktaGroups) == 0 {
|
||||
errString := fmt.Sprintf(
|
||||
"no Okta groups found; only policies from locally-defined groups available")
|
||||
oktaResponse.AddWarning(errString)
|
||||
}
|
||||
allGroups = append(allGroups, oktaGroups...)
|
||||
}
|
||||
|
||||
var allGroups []string
|
||||
// Import the custom added groups from okta backend
|
||||
user, err := b.User(req.Storage, username)
|
||||
if err != nil {
|
||||
@ -122,8 +115,6 @@ func (b *backend) Login(req *logical.Request, username string, password string)
|
||||
}
|
||||
allGroups = append(allGroups, user.Groups...)
|
||||
}
|
||||
// Merge local and Okta groups
|
||||
allGroups = append(allGroups, oktaGroups...)
|
||||
|
||||
// Retrieve policies
|
||||
var policies []string
|
||||
@ -157,6 +148,24 @@ func (b *backend) Login(req *logical.Request, username string, password string)
|
||||
return policies, oktaResponse, nil
|
||||
}
|
||||
|
||||
func (b *backend) getOktaGroups(client *okta.Client, user *okta.User) ([]string, error) {
|
||||
rsp, err := client.Users.PopulateGroups(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, fmt.Errorf("okta auth backend unexpected failure")
|
||||
}
|
||||
oktaGroups := make([]string, 0, len(user.Groups))
|
||||
for _, group := range user.Groups {
|
||||
oktaGroups = append(oktaGroups, group.Profile.Name)
|
||||
}
|
||||
if b.Logger().IsDebug() {
|
||||
b.Logger().Debug("auth/okta: Groups fetched from Okta", "num_groups", len(oktaGroups), "groups", oktaGroups)
|
||||
}
|
||||
return oktaGroups, nil
|
||||
}
|
||||
|
||||
const backendHelp = `
|
||||
The Okta credential provider allows authentication querying,
|
||||
checking username and password, and associating policies. If an api token is configure
|
||||
|
@ -3,7 +3,6 @@ package okta
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
|
||||
@ -13,13 +12,18 @@ import (
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "okta.com"
|
||||
previewBaseURL = "oktapreview.com"
|
||||
)
|
||||
|
||||
func pathConfig(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: `config`,
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"organization": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Okta organization to authenticate against (DEPRECATED)",
|
||||
Description: "(DEPRECATED) Okta organization to authenticate against. Use org_name instead.",
|
||||
},
|
||||
"org_name": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
@ -27,7 +31,7 @@ func pathConfig(b *backend) *framework.Path {
|
||||
},
|
||||
"token": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Okta admin API token (DEPRECATED)",
|
||||
Description: "(DEPRECATED) Okta admin API token. Use api_token instead.",
|
||||
},
|
||||
"api_token": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
@ -35,13 +39,11 @@ func pathConfig(b *backend) *framework.Path {
|
||||
},
|
||||
"base_url": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The API endpoint to use. Useful if you
|
||||
are using Okta development accounts. (DEPRECATED)`,
|
||||
Description: `The base domain to use for the Okta API. When not specified in the configuraiton, "okta.com" is used.`,
|
||||
},
|
||||
"production": &framework.FieldSchema{
|
||||
Type: framework.TypeBool,
|
||||
Default: true,
|
||||
Description: `If set, production API URL prefix will be used to communicate with Okta and if not set, a preview production API URL prefix will be used. Defaults to true.`,
|
||||
Description: `(DEPRECATED) Use base_url.`,
|
||||
},
|
||||
"ttl": &framework.FieldSchema{
|
||||
Type: framework.TypeDurationSecond,
|
||||
@ -100,7 +102,6 @@ func (b *backend) pathConfigRead(
|
||||
Data: map[string]interface{}{
|
||||
"organization": cfg.Org,
|
||||
"org_name": cfg.Org,
|
||||
"production": *cfg.Production,
|
||||
"ttl": cfg.TTL,
|
||||
"max_ttl": cfg.MaxTTL,
|
||||
},
|
||||
@ -108,6 +109,9 @@ func (b *backend) pathConfigRead(
|
||||
if cfg.BaseURL != "" {
|
||||
resp.Data["base_url"] = cfg.BaseURL
|
||||
}
|
||||
if cfg.Production != nil {
|
||||
resp.Data["production"] = *cfg.Production
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
@ -149,26 +153,29 @@ func (b *backend) pathConfigWrite(
|
||||
cfg.Token = token.(string)
|
||||
}
|
||||
}
|
||||
if cfg.Token == "" && req.Operation == logical.CreateOperation {
|
||||
return logical.ErrorResponse("api_token is missing"), nil
|
||||
}
|
||||
|
||||
baseURL, ok := d.GetOk("base_url")
|
||||
baseURLRaw, ok := d.GetOk("base_url")
|
||||
if ok {
|
||||
baseURLString := baseURL.(string)
|
||||
if len(baseURLString) != 0 {
|
||||
_, err = url.Parse(baseURLString)
|
||||
baseURL := baseURLRaw.(string)
|
||||
_, err = url.Parse(fmt.Sprintf("https://%s,%s", cfg.Org, baseURL))
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("Error parsing given base_url: %s", err)), nil
|
||||
}
|
||||
cfg.BaseURL = baseURLString
|
||||
}
|
||||
} else if req.Operation == logical.CreateOperation {
|
||||
cfg.BaseURL = d.Get("base_url").(string)
|
||||
cfg.BaseURL = baseURL
|
||||
}
|
||||
|
||||
productionRaw := d.Get("production").(bool)
|
||||
cfg.Production = &productionRaw
|
||||
// We only care about the production flag when base_url is not set. It is
|
||||
// for compatibility reasons.
|
||||
if cfg.BaseURL == "" {
|
||||
productionRaw, ok := d.GetOk("production")
|
||||
if ok {
|
||||
production := productionRaw.(bool)
|
||||
cfg.Production = &production
|
||||
}
|
||||
} else {
|
||||
// clear out old production flag if base_url is set
|
||||
cfg.Production = nil
|
||||
}
|
||||
|
||||
ttl, ok := d.GetOk("ttl")
|
||||
if ok {
|
||||
@ -207,16 +214,19 @@ func (b *backend) pathConfigExistenceCheck(
|
||||
|
||||
// OktaClient creates a basic okta client connection
|
||||
func (c *ConfigEntry) OktaClient() *okta.Client {
|
||||
production := true
|
||||
baseURL := defaultBaseURL
|
||||
if c.Production != nil {
|
||||
production = *c.Production
|
||||
if !*c.Production {
|
||||
baseURL = previewBaseURL
|
||||
}
|
||||
}
|
||||
if c.BaseURL != "" {
|
||||
if strings.Contains(c.BaseURL, "oktapreview.com") {
|
||||
production = false
|
||||
baseURL = c.BaseURL
|
||||
}
|
||||
}
|
||||
return okta.NewClient(cleanhttp.DefaultClient(), c.Org, c.Token, production)
|
||||
|
||||
// We validate config on input and errors are only returned when parsing URLs
|
||||
client, _ := okta.NewClientWithDomain(cleanhttp.DefaultClient(), c.Org, baseURL, c.Token)
|
||||
return client
|
||||
}
|
||||
|
||||
// ConfigEntry for Okta
|
||||
|
40
vendor/github.com/chrismalek/oktasdk-go/okta/sdk.go
generated
vendored
40
vendor/github.com/chrismalek/oktasdk-go/okta/sdk.go
generated
vendored
@ -21,8 +21,9 @@ import (
|
||||
const (
|
||||
libraryVersion = "1"
|
||||
userAgent = "oktasdk-go/" + libraryVersion
|
||||
productionURLFormat = "https://%s.okta.com/api/v1/"
|
||||
previewProductionURLFormat = "https://%s.oktapreview.com/api/v1/"
|
||||
productionDomain = "okta.com"
|
||||
previewDomain = "oktapreview.com"
|
||||
urlFormat = "https://%s.%s/api/v1/"
|
||||
headerRateLimit = "X-Rate-Limit-Limit"
|
||||
headerRateRemaining = "X-Rate-Limit-Remaining"
|
||||
headerRateReset = "X-Rate-Limit-Reset"
|
||||
@ -94,19 +95,38 @@ type service struct {
|
||||
// NewClient returns a new OKTA API client. If a nil httpClient is
|
||||
// provided, http.DefaultClient will be used.
|
||||
func NewClient(httpClient *http.Client, orgName string, apiToken string, isProduction bool) *Client {
|
||||
var baseDomain string
|
||||
if isProduction {
|
||||
baseDomain = productionDomain
|
||||
} else {
|
||||
baseDomain = previewDomain
|
||||
}
|
||||
client, _ := NewClientWithDomain(httpClient, orgName, baseDomain, apiToken)
|
||||
return client
|
||||
}
|
||||
|
||||
// NewClientWithDomain creates a client based on the organziation name and
|
||||
// base domain for requests (okta.com, okta-emea.com, oktapreview.com, etc).
|
||||
func NewClientWithDomain(httpClient *http.Client, orgName string, domain string, apiToken string) (*Client, error) {
|
||||
baseURL, err := url.Parse(fmt.Sprintf(urlFormat, orgName, domain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClientWithBaseURL(httpClient, baseURL, apiToken), nil
|
||||
}
|
||||
|
||||
// NewClientWithBaseURL creates a client based on the full base URL and api
|
||||
// token
|
||||
func NewClientWithBaseURL(httpClient *http.Client, baseURL *url.URL, apiToken string) *Client {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
|
||||
var baseURL *url.URL
|
||||
if isProduction {
|
||||
baseURL, _ = url.Parse(fmt.Sprintf(productionURLFormat, orgName))
|
||||
} else {
|
||||
baseURL, _ = url.Parse(fmt.Sprintf(previewProductionURLFormat, orgName))
|
||||
|
||||
c := &Client{
|
||||
client: httpClient,
|
||||
BaseURL: baseURL,
|
||||
UserAgent: userAgent,
|
||||
}
|
||||
|
||||
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
|
||||
c.PauseOnRateLimit = true // If rate limit found it will block until that time. If false then Error will be returned
|
||||
c.authorizationHeaderValue = fmt.Sprintf(headerAuthorizationFormat, apiToken)
|
||||
c.apiKey = apiToken
|
||||
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -439,10 +439,10 @@
|
||||
"revisionTime": "2017-07-11T19:02:43Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QZtBo/fc3zeQFxPFgPVMyDiw70M=",
|
||||
"checksumSHA1": "sFjc2R+KS9AeXIPMV4KCw+GwX5I=",
|
||||
"path": "github.com/chrismalek/oktasdk-go/okta",
|
||||
"revision": "7d4ce0a254ec5f9eda3397523f6cf183e1d46c5e",
|
||||
"revisionTime": "2017-02-07T05:01:14Z"
|
||||
"revision": "ae553c909ca06a4c34eb41ee435e83871a7c2496",
|
||||
"revisionTime": "2017-09-11T15:31:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "WsB6y1Yd+kDbHGz1Rm7xZ44hyAE=",
|
||||
|
@ -29,10 +29,11 @@ distinction between the `create` and `update` capabilities inside ACL policies.
|
||||
|
||||
- `org_name` `(string: <required>)` - Name of the organization to be used in the
|
||||
Okta API.
|
||||
- `api_token` `(string: <required>)` - Okta API key.
|
||||
- `production` `(bool: true)` - If set, production API URL prefix will be used
|
||||
to communicate with Okta and if not set, a preview production API URL prefix
|
||||
will be used. Defaults to true.
|
||||
- `api_token` `(string: "")` - Okta API token. This is required to query Okta
|
||||
for user group membership. If this is not supplied only locally configured
|
||||
groups will be enabled.
|
||||
- `base_url` `(string: "")` - If set, will be used as the base domain
|
||||
for API requests. Examples are okta.com, oktapreview.com, and okta-emea.com.
|
||||
- `ttl` `(string: "")` - Duration after which authentication will be expired.
|
||||
- `max_ttl` `(string: "")` - Maximum duration after which authentication will
|
||||
be expired.
|
||||
@ -83,7 +84,7 @@ $ curl \
|
||||
"data": {
|
||||
"org_name": "example",
|
||||
"api_token": "abc123",
|
||||
"production": true,
|
||||
"base_url": "okta.com",
|
||||
"ttl": "",
|
||||
"max_ttl": ""
|
||||
},
|
||||
|
@ -22,6 +22,9 @@ This endpoint defines a MFA method of type Duo.
|
||||
|
||||
- `username_format` `(string)` - A format string for mapping Identity names to MFA method names. Values to substitute should be placed in `{{}}`. For example, `"{{persona.name}}@example.com"`. If blank, the Persona's Name field will be used as-is. Currently-supported mappings:
|
||||
- persona.name: The name returned by the mount configured via the `mount_accessor` parameter
|
||||
- entity.name: The name configured for the Entity
|
||||
- persona.metadata.`<key>`: The value of the Persona's metadata parameter
|
||||
- entity.metadata.`<key>`: The value of the Entity's metadata paramater
|
||||
|
||||
- `secret_key` `(string)` - Secret key for Duo.
|
||||
|
||||
|
@ -22,12 +22,15 @@ This endpoint defines a MFA method of type Okta.
|
||||
|
||||
- `username_format` `(string)` - A format string for mapping Identity names to MFA method names. Values to substitute should be placed in `{{}}`. For example, `"{{persona.name}}@example.com"`. If blank, the Persona's Name field will be used as-is. Currently-supported mappings:
|
||||
- persona.name: The name returned by the mount configured via the `mount_accessor` parameter
|
||||
- entity.name: The name configured for the Entity
|
||||
- persona.metadata.`<key>`: The value of the Persona's metadata parameter
|
||||
- entity.metadata.`<key>`: The value of the Entity's metadata paramater
|
||||
|
||||
- `org_name` `(string)` - Name of the organization to be used in the Okta API.
|
||||
|
||||
- `api_token` `(string)` - Okta API key.
|
||||
|
||||
- `production` `(string)` - If set, production API URL prefix will be used to communicate with Okta and if not set, a preview production API URL prefix will be used. Defaults to true.
|
||||
- `base_url` `(string)` - If set, will be used as the base domain for API requests. Examples are okta.com, oktapreview.com, and okta-emea.com.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
|
@ -22,6 +22,9 @@ This endpoint defines a MFA method of type PingID.
|
||||
|
||||
- `username_format` `(string)` - A format string for mapping Identity names to MFA method names. Values to substitute should be placed in `{{}}`. For example, `"{{persona.name}}@example.com"`. If blank, the Persona's Name field will be used as-is. Currently-supported mappings:
|
||||
- persona.name: The name returned by the mount configured via the `mount_accessor` parameter
|
||||
- entity.name: The name configured for the Entity
|
||||
- persona.metadata.`<key>`: The value of the Persona's metadata parameter
|
||||
- entity.metadata.`<key>`: The value of the Entity's metadata paramater
|
||||
|
||||
- `settings_file_base64` `(string)` - A base64-encoded third-party settings file retrieved from PingID's configuration page.
|
||||
|
||||
|
@ -87,8 +87,8 @@ Configuration is written to `auth/okta/config`.
|
||||
|
||||
### Connection parameters
|
||||
|
||||
* `organization` (string, required) - The Okta organization. This will be the first part of the url `https://XXX.okta.com` url.
|
||||
* `token` (string, optional) - The Okta API token. This is required to query Okta for user group membership. If this is not supplied only locally configured groups will be enabled. This can be generated from http://developer.okta.com/docs/api/getting_started/getting_a_token.html
|
||||
* `org_name` (string, required) - The Okta organization. This will be the first part of the url `https://XXX.okta.com` url.
|
||||
* `api_token` (string, optional) - The Okta API token. This is required to query Okta for user group membership. If this is not supplied only locally configured groups will be enabled. This can be generated from http://developer.okta.com/docs/api/getting_started/getting_a_token.html
|
||||
* `base_url` (string, optional) - The Okta url. Examples: `oktapreview.com`, The default is `okta.com`
|
||||
* `max_ttl` (string, optional) - Maximum duration after which authentication will be expired.
|
||||
Either number of seconds or in a format parsable by Go's [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)
|
||||
@ -106,7 +106,7 @@ Use `vault path-help` for more details.
|
||||
|
||||
```
|
||||
$ vault write auth/okta/config \
|
||||
organization="XXXTest"
|
||||
org_name="XXXTest"
|
||||
...
|
||||
```
|
||||
|
||||
@ -118,8 +118,8 @@ $ vault write auth/okta/config \
|
||||
|
||||
```
|
||||
$ vault write auth/okta/config base_url="oktapreview.com" \
|
||||
organization="dev-123456" \
|
||||
token="00KzlTNCqDf0enpQKYSAYUt88KHqXax6dT11xEZz_g"
|
||||
org_name="dev-123456" \
|
||||
api_token="00KzlTNCqDf0enpQKYSAYUt88KHqXax6dT11xEZz_g"
|
||||
...
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user