Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for base_url for Okta api #3316

Merged
merged 9 commits into from
Sep 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 32 additions & 23 deletions builtin/credential/okta/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{},
}
if len(oktaGroups) == 0 {
errString := fmt.Sprintf(
"no Okta groups found; only policies from locally-defined groups available")
oktaResponse.AddWarning(errString)
}

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...)
}

// Import the custom added groups from okta backend
user, err := b.User(req.Storage, username)
if err != nil {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
70 changes: 40 additions & 30 deletions builtin/credential/okta/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package okta
import (
"fmt"
"net/url"
"strings"

"time"

Expand All @@ -13,35 +12,38 @@ 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,
Description: "Name of the organization to be used in the Okta API.",
},
"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,
Description: "Okta API key.",
},
"base_url": &framework.FieldSchema{
Type: framework.TypeString,
Description: `The API endpoint to use. Useful if you
are using Okta development accounts. (DEPRECATED)`,
Type: framework.TypeString,
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,
Expand Down Expand Up @@ -100,14 +102,16 @@ 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,
},
}
if cfg.BaseURL != "" {
resp.Data["base_url"] = cfg.BaseURL
}
if cfg.Production != nil {
resp.Data["production"] = *cfg.Production
}

return resp, nil
}
Expand Down Expand Up @@ -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)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Error parsing given base_url: %s", err)), nil
}
cfg.BaseURL = 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
}
} 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 {
Expand Down Expand Up @@ -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
Expand Down
60 changes: 40 additions & 20 deletions vendor/github.com/chrismalek/oktasdk-go/okta/sdk.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions vendor/vendor.json
Original file line number Diff line number Diff line change
Expand Up @@ -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=",
Expand Down
11 changes: 6 additions & 5 deletions website/source/api/auth/okta/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -83,7 +84,7 @@ $ curl \
"data": {
"org_name": "example",
"api_token": "abc123",
"production": true,
"base_url": "okta.com",
"ttl": "",
"max_ttl": ""
},
Expand Down
3 changes: 3 additions & 0 deletions website/source/api/system/mfa-duo.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 4 additions & 1 deletion website/source/api/system/mfa-okta.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions website/source/api/system/mfa-pingid.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading