open-vault/builtin/credential/okta/path_config.go

361 lines
9.4 KiB
Go
Raw Normal View History

2017-01-27 00:08:52 +00:00
package okta
import (
"context"
2017-01-27 00:08:52 +00:00
"fmt"
"net/http"
2017-01-27 00:08:52 +00:00
"net/url"
"strings"
"time"
oktaold "github.com/chrismalek/oktasdk-go/okta"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/sdk/framework"
2019-07-01 20:30:30 +00:00
"github.com/hashicorp/vault/sdk/helper/tokenutil"
"github.com/hashicorp/vault/sdk/logical"
oktanew "github.com/okta/okta-sdk-golang/v2/okta"
2017-01-27 00:08:52 +00:00
)
const (
defaultBaseURL = "okta.com"
previewBaseURL = "oktapreview.com"
)
2017-01-27 00:08:52 +00:00
func pathConfig(b *backend) *framework.Path {
2019-07-01 20:30:30 +00:00
p := &framework.Path{
2017-01-27 00:08:52 +00:00
Pattern: `config`,
Fields: map[string]*framework.FieldSchema{
"organization": {
2017-01-27 00:08:52 +00:00
Type: framework.TypeString,
Description: "Use org_name instead.",
Deprecated: true,
},
"org_name": {
Type: framework.TypeString,
Description: "Name of the organization to be used in the Okta API.",
DisplayAttrs: &framework.DisplayAttributes{
Name: "Organization Name",
},
2017-01-27 00:08:52 +00:00
},
"token": {
2017-01-27 00:08:52 +00:00
Type: framework.TypeString,
Description: "Use api_token instead.",
Deprecated: true,
},
"api_token": {
Type: framework.TypeString,
Description: "Okta API key.",
DisplayAttrs: &framework.DisplayAttributes{
Name: "API Token",
},
2017-01-27 00:08:52 +00:00
},
"base_url": {
Type: framework.TypeString,
2018-03-20 18:54:10 +00:00
Description: `The base domain to use for the Okta API. When not specified in the configuration, "okta.com" is used.`,
DisplayAttrs: &framework.DisplayAttributes{
Name: "Base URL",
},
},
"production": {
Type: framework.TypeBool,
Description: `Use base_url instead.`,
Deprecated: true,
2017-01-27 00:08:52 +00:00
},
"ttl": {
Type: framework.TypeDurationSecond,
2019-07-01 20:30:30 +00:00
Description: tokenutil.DeprecationText("token_ttl"),
Deprecated: true,
},
"max_ttl": {
Type: framework.TypeDurationSecond,
2019-07-01 20:30:30 +00:00
Description: tokenutil.DeprecationText("token_max_ttl"),
Deprecated: true,
},
"bypass_okta_mfa": {
Type: framework.TypeBool,
Description: `When set true, requests by Okta for a MFA check will be bypassed. This also disallows certain status checks on the account, such as whether the password is expired.`,
DisplayAttrs: &framework.DisplayAttributes{
Name: "Bypass Okta MFA",
},
},
2017-01-27 00:08:52 +00:00
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathConfigRead,
logical.CreateOperation: b.pathConfigWrite,
logical.UpdateOperation: b.pathConfigWrite,
},
ExistenceCheck: b.pathConfigExistenceCheck,
HelpSynopsis: pathConfigHelp,
Enable generated items for more auth methods (#7513) * enable auth method item configuration in go code * properly parse and list generated items * make sure we only set name on attrs if a label comes from openAPI * correctly construct paths object for method index route * set sensitive property on password for userpass * remove debugger statements * pass method model to list route template to use paths on model for tabs * update tab generation in generated item list, undo enabling userpass users * enable openapi generated itams for certs and userpass, update ldap to no longer have action on list endpoint * add editType to DisplayAttributes, pull tokenutil fields into field group * show sensitive message for sensitive fields displayed in fieldGroupShow component * grab sensitive and editType fields from displayAttrs in openapi-to-attrs util * make sure we don't ask for paths for secret backends since that isn't setup yet * fix styling of sensitive text for fieldGroupShow component * update openapi-to-attrs util test to no longer include label by default, change debugger to console.err in path-help, remove dynamic ui auth methods from tab count test * properly log errors to the console * capitalize This value is sensitive... * get rid of extra padding on bottom of fieldgroupshow * make auth methods clickable and use new confirm ux * Update sdk/framework/path.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * Update sdk/framework/path.go Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com> * add whitespace * return intErr instead of err * uncomment out helpUrl because we need it * remove extra box class * use const instead of let * remove extra conditional since we already split the pathName later on * ensure we request the correct url when listing generated items * use const * link to list and show pages * remove dead code * show nested item name instead of id * add comments * show tooltip for text-file inputs * fix storybook * remove extra filter * add TODOs * add comments * comment out unused variables but leave them in function signature * only link to auth methods that can be fully managed in the ui * clean up comments * only render tooltip if there is helpText * rename id authMethodPath * remove optionsForQuery since we don't need it * add indentation * standardize ConfirmMessage and show model name instead of id when editing * standardize ConfirmMessage and show model name instead of id when editing * add comments * post to the correct updateUrl so we can edit users and groups * use pop instead of slice * add TODO for finding a better way to store ids * ensure ids are handled the same way on list and show pages; fix editing and deleting * add comment about difference between list and show urls * use model.id instead of name since we do not need it * remove dead code * ensure list pages have page headers * standardize using authMethodPath instead of method and remove dead code * i love indentation * remove more dead code * use new Confirm * show correct flash message when deleting an item * update flash message for creating and updating * use plus icon for creating group/user instead of an arrow
2019-10-17 23:19:14 +00:00
DisplayAttrs: &framework.DisplayAttributes{
Action: "Configure",
},
2017-01-27 00:08:52 +00:00
}
2019-07-01 20:30:30 +00:00
tokenutil.AddTokenFields(p.Fields)
p.Fields["token_policies"].Description += ". This will apply to all tokens generated by this auth method, in addition to any configured for specific users/groups."
return p
2017-01-27 00:08:52 +00:00
}
// Config returns the configuration for this backend.
func (b *backend) Config(ctx context.Context, s logical.Storage) (*ConfigEntry, error) {
entry, err := s.Get(ctx, "config")
2017-01-27 00:08:52 +00:00
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var result ConfigEntry
if entry != nil {
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}
}
2019-07-01 20:30:30 +00:00
if result.TokenTTL == 0 && result.TTL > 0 {
result.TokenTTL = result.TTL
}
if result.TokenMaxTTL == 0 && result.MaxTTL > 0 {
result.TokenMaxTTL = result.MaxTTL
}
2017-01-27 00:08:52 +00:00
return &result, nil
}
func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
cfg, err := b.Config(ctx, req.Storage)
2017-01-27 00:08:52 +00:00
if err != nil {
return nil, err
}
if cfg == nil {
return nil, nil
}
2019-07-01 20:30:30 +00:00
data := map[string]interface{}{
"organization": cfg.Org,
"org_name": cfg.Org,
"bypass_okta_mfa": cfg.BypassOktaMFA,
2017-01-27 00:08:52 +00:00
}
2019-07-01 20:30:30 +00:00
cfg.PopulateTokenData(data)
if cfg.BaseURL != "" {
2019-07-01 20:30:30 +00:00
data["base_url"] = cfg.BaseURL
}
if cfg.Production != nil {
2019-07-01 20:30:30 +00:00
data["production"] = *cfg.Production
}
if cfg.TTL > 0 {
data["ttl"] = int64(cfg.TTL.Seconds())
}
if cfg.MaxTTL > 0 {
data["max_ttl"] = int64(cfg.MaxTTL.Seconds())
}
resp := &logical.Response{
Data: data,
}
2017-01-27 00:08:52 +00:00
if cfg.BypassOktaMFA {
resp.AddWarning("Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases.")
}
2017-01-27 00:08:52 +00:00
return resp, nil
}
func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
cfg, err := b.Config(ctx, req.Storage)
2017-01-27 00:08:52 +00:00
if err != nil {
return nil, err
}
// Due to the existence check, entry will only be nil if it's a create
// operation, so just create a new one
if cfg == nil {
cfg = &ConfigEntry{}
}
org, ok := d.GetOk("org_name")
if ok {
cfg.Org = org.(string)
}
if cfg.Org == "" {
org, ok = d.GetOk("organization")
if ok {
cfg.Org = org.(string)
}
}
if cfg.Org == "" && req.Operation == logical.CreateOperation {
return logical.ErrorResponse("org_name is missing"), nil
2017-01-27 00:08:52 +00:00
}
token, ok := d.GetOk("api_token")
2017-01-27 00:08:52 +00:00
if ok {
cfg.Token = token.(string)
} else if token, ok = d.GetOk("token"); ok {
cfg.Token = token.(string)
}
2017-01-27 00:08:52 +00:00
baseURLRaw, ok := d.GetOk("base_url")
2017-01-27 00:08:52 +00:00
if ok {
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
2017-01-27 00:08:52 +00:00
}
cfg.BaseURL = baseURL
2017-01-27 00:08:52 +00:00
}
// 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
}
bypass, ok := d.GetOk("bypass_okta_mfa")
if ok {
cfg.BypassOktaMFA = bypass.(bool)
}
2019-07-01 20:30:30 +00:00
if err := cfg.ParseTokenFields(req, d); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
2019-07-01 20:30:30 +00:00
// Handle upgrade cases
{
if err := tokenutil.UpgradeValue(d, "ttl", "token_ttl", &cfg.TTL, &cfg.TokenTTL); err != nil {
return logical.ErrorResponse(err.Error()), nil
2019-07-01 20:30:30 +00:00
}
if err := tokenutil.UpgradeValue(d, "max_ttl", "token_max_ttl", &cfg.MaxTTL, &cfg.TokenMaxTTL); err != nil {
return logical.ErrorResponse(err.Error()), nil
2019-07-01 20:30:30 +00:00
}
}
2017-01-27 00:08:52 +00:00
jsonCfg, err := logical.StorageEntryJSON("config", cfg)
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, jsonCfg); err != nil {
2017-01-27 00:08:52 +00:00
return nil, err
}
var resp *logical.Response
if cfg.BypassOktaMFA {
resp = new(logical.Response)
resp.AddWarning("Okta MFA bypass is configured. In addition to ignoring Okta MFA requests, certain other account statuses will not be seen, such as PASSWORD_EXPIRED. Authentication will succeed in these cases.")
}
return resp, nil
2017-01-27 00:08:52 +00:00
}
func (b *backend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
cfg, err := b.Config(ctx, req.Storage)
2017-01-27 00:08:52 +00:00
if err != nil {
return false, err
}
return cfg != nil, nil
}
type oktaShim interface {
Client() (*oktanew.Client, context.Context)
NewRequest(method string, url string, body interface{}) (*http.Request, error)
Do(req *http.Request, v interface{}) (interface{}, error)
}
type oktaShimNew struct {
client *oktanew.Client
ctx context.Context
}
func (new *oktaShimNew) Client() (*oktanew.Client, context.Context) {
return new.client, new.ctx
}
func (new *oktaShimNew) NewRequest(method string, url string, body interface{}) (*http.Request, error) {
if !strings.HasPrefix(url, "/") {
url = "/api/v1/" + url
}
return new.client.GetRequestExecutor().NewRequest(method, url, body)
}
func (new *oktaShimNew) Do(req *http.Request, v interface{}) (interface{}, error) {
return new.client.GetRequestExecutor().Do(new.ctx, req, v)
}
type oktaShimOld struct {
client *oktaold.Client
}
func (new *oktaShimOld) Client() (*oktanew.Client, context.Context) {
return nil, nil
}
func (new *oktaShimOld) NewRequest(method string, url string, body interface{}) (*http.Request, error) {
return new.client.NewRequest(method, url, body)
}
func (new *oktaShimOld) Do(req *http.Request, v interface{}) (interface{}, error) {
return new.client.Do(req, v)
}
2017-01-27 00:08:52 +00:00
// OktaClient creates a basic okta client connection
func (c *ConfigEntry) OktaClient(ctx context.Context) (oktaShim, error) {
baseURL := defaultBaseURL
if c.Production != nil {
if !*c.Production {
baseURL = previewBaseURL
}
2017-01-27 00:08:52 +00:00
}
if c.BaseURL != "" {
baseURL = c.BaseURL
2017-01-27 00:08:52 +00:00
}
if c.Token != "" {
ctx, client, err := oktanew.NewClient(ctx,
oktanew.WithOrgUrl("https://"+c.Org+"."+baseURL),
oktanew.WithToken(c.Token))
if err != nil {
return nil, err
}
return &oktaShimNew{client, ctx}, nil
}
client, err := oktaold.NewClientWithDomain(cleanhttp.DefaultClient(), c.Org, baseURL, "")
if err != nil {
return nil, err
}
return &oktaShimOld{client}, nil
2017-01-27 00:08:52 +00:00
}
// ConfigEntry for Okta
type ConfigEntry struct {
2019-07-01 20:30:30 +00:00
tokenutil.TokenParams
Org string `json:"organization"`
Token string `json:"token"`
BaseURL string `json:"base_url"`
Production *bool `json:"is_production,omitempty"`
TTL time.Duration `json:"ttl"`
MaxTTL time.Duration `json:"max_ttl"`
BypassOktaMFA bool `json:"bypass_okta_mfa"`
2017-01-27 00:08:52 +00:00
}
const pathConfigHelp = `
This endpoint allows you to configure the Okta and its
configuration options.
The Okta organization are the characters at the front of the URL for Okta.
Example https://ORG.okta.com
`