diff --git a/builtin/credential/okta/backend_test.go b/builtin/credential/okta/backend_test.go index 7672dc09982e..30e917017820 100644 --- a/builtin/credential/okta/backend_test.go +++ b/builtin/credential/okta/backend_test.go @@ -12,12 +12,18 @@ import ( "github.com/hashicorp/vault/logical" logicaltest "github.com/hashicorp/vault/logical/testing" + "time" ) func TestBackend_Config(t *testing.T) { + defaultLeaseTTLVal := time.Hour * 12 + maxLeaseTTLVal := time.Hour * 24 b, err := Factory(&logical.BackendConfig{ Logger: logformat.NewVaultLogger(log.LevelTrace), - System: &logical.StaticSystemView{}, + System: &logical.StaticSystemView{ + DefaultLeaseTTLVal: defaultLeaseTTLVal, + MaxLeaseTTLVal: maxLeaseTTLVal, + }, }) if err != nil { t.Fatalf("Unable to create backend: %s", err) @@ -25,14 +31,17 @@ func TestBackend_Config(t *testing.T) { username := os.Getenv("OKTA_USERNAME") password := os.Getenv("OKTA_PASSWORD") + token := os.Getenv("OKTA_API_TOKEN") configData := map[string]interface{}{ "organization": os.Getenv("OKTA_ORG"), "base_url": "oktapreview.com", } + updatedDuration := time.Hour * 1 configDataToken := map[string]interface{}{ - "token": os.Getenv("OKTA_API_TOKEN"), + "token": token, + "ttl": "1h", } logicaltest.Test(t, logicaltest.TestCase{ @@ -41,23 +50,23 @@ func TestBackend_Config(t *testing.T) { Backend: b, Steps: []logicaltest.TestStep{ testConfigCreate(t, configData), - testLoginWrite(t, username, "wrong", "E0000004", nil), - testLoginWrite(t, username, password, "user is not a member of any authorized policy", nil), + testLoginWrite(t, username, "wrong", "E0000004", 0, nil), + testLoginWrite(t, username, password, "user is not a member of any authorized policy", 0, nil), testAccUserGroups(t, username, "local_group,local_group2"), testAccGroups(t, "local_group", "local_group_policy"), - testLoginWrite(t, username, password, "", []string{"local_group_policy"}), + testLoginWrite(t, username, password, "", defaultLeaseTTLVal, []string{"local_group_policy"}), testAccGroups(t, "Everyone", "everyone_group_policy,every_group_policy2"), - testLoginWrite(t, username, password, "", []string{"local_group_policy"}), + testLoginWrite(t, username, password, "", defaultLeaseTTLVal, []string{"local_group_policy"}), testConfigUpdate(t, configDataToken), - testConfigRead(t, configData), - testLoginWrite(t, username, password, "", []string{"everyone_group_policy", "every_group_policy2", "local_group_policy"}), + testConfigRead(t, token, configData), + testLoginWrite(t, username, password, "", updatedDuration, []string{"everyone_group_policy", "every_group_policy2", "local_group_policy"}), testAccGroups(t, "local_group2", "testgroup_group_policy"), - testLoginWrite(t, username, password, "", []string{"everyone_group_policy", "every_group_policy2", "local_group_policy", "testgroup_group_policy"}), + testLoginWrite(t, username, password, "", updatedDuration, []string{"everyone_group_policy", "every_group_policy2", "local_group_policy", "testgroup_group_policy"}), }, }) } -func testLoginWrite(t *testing.T, username, password, reason string, policies []string) logicaltest.TestStep { +func testLoginWrite(t *testing.T, username, password, reason string, expectedTTL time.Duration, policies []string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "login/" + username, @@ -76,6 +85,11 @@ func testLoginWrite(t *testing.T, username, password, reason string, policies [] if !policyutil.EquivalentPolicies(resp.Auth.Policies, policies) { return fmt.Errorf("policy mismatch expected %v but got %v", policies, resp.Auth.Policies) } + + actualTTL := resp.Auth.LeaseOptions.TTL + if actualTTL != expectedTTL { + return fmt.Errorf("TTL mismatch expected %v but got %v", expectedTTL, actualTTL) + } } return nil @@ -99,7 +113,7 @@ func testConfigUpdate(t *testing.T, d map[string]interface{}) logicaltest.TestSt } } -func testConfigRead(t *testing.T, d map[string]interface{}) logicaltest.TestStep { +func testConfigRead(t *testing.T, token string, d map[string]interface{}) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "config", @@ -108,16 +122,18 @@ func testConfigRead(t *testing.T, d map[string]interface{}) logicaltest.TestStep return resp.Error() } - if resp.Data["Org"] != d["organization"] { + if resp.Data["organization"] != d["organization"] { return fmt.Errorf("Org mismatch expected %s but got %s", d["organization"], resp.Data["Org"]) } - if resp.Data["BaseURL"] != d["base_url"] { + if resp.Data["base_url"] != d["base_url"] { return fmt.Errorf("BaseURL mismatch expected %s but got %s", d["base_url"], resp.Data["BaseURL"]) } - if _, exists := resp.Data["Token"]; exists { - return fmt.Errorf("token should not be returned on a read request") + for _, value := range resp.Data { + if value == token { + return fmt.Errorf("token should not be returned on a read request") + } } return nil diff --git a/builtin/credential/okta/path_config.go b/builtin/credential/okta/path_config.go index b454f7ee2a6e..f3eafe8667ce 100644 --- a/builtin/credential/okta/path_config.go +++ b/builtin/credential/okta/path_config.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" "github.com/sstarcher/go-okta" + "time" ) func pathConfig(b *backend) *framework.Path { @@ -26,6 +27,14 @@ func pathConfig(b *backend) *framework.Path { Description: `The API endpoint to use. Useful if you are using Okta development accounts.`, }, + "ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `Duration after which authentication will be expired`, + }, + "max_ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: `Maximum duration after which authentication will be expired`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -73,8 +82,10 @@ func (b *backend) pathConfigRead( resp := &logical.Response{ Data: map[string]interface{}{ - "Org": cfg.Org, - "BaseURL": cfg.BaseURL, + "organization": cfg.Org, + "base_url": cfg.BaseURL, + "ttl": cfg.TTL, + "max_ttl": cfg.MaxTTL, }, } @@ -118,6 +129,12 @@ func (b *backend) pathConfigWrite( cfg.BaseURL = d.Get("base_url").(string) } + ttl := d.Get("ttl").(int) + cfg.TTL = time.Duration(ttl) * time.Second + + maxTTL := d.Get("max_ttl").(int) + cfg.MaxTTL = time.Duration(maxTTL) * time.Second + jsonCfg, err := logical.StorageEntryJSON("config", cfg) if err != nil { return nil, err @@ -155,9 +172,11 @@ func (c *ConfigEntry) OktaClient() *okta.Client { // ConfigEntry for Okta type ConfigEntry struct { - Org string `json:"organization"` - Token string `json:"token"` - BaseURL string `json:"base_url"` + Org string `json:"organization"` + Token string `json:"token"` + BaseURL string `json:"base_url"` + TTL time.Duration `json:"ttl"` + MaxTTL time.Duration `json:"max_ttl"` } const pathConfigHelp = ` diff --git a/builtin/credential/okta/path_login.go b/builtin/credential/okta/path_login.go index accc867bda2d..e439771448db 100644 --- a/builtin/credential/okta/path_login.go +++ b/builtin/credential/okta/path_login.go @@ -5,6 +5,7 @@ import ( "sort" "strings" + "github.com/go-errors/errors" "github.com/hashicorp/vault/helper/policyutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" @@ -55,6 +56,11 @@ func (b *backend) pathLogin( sort.Strings(policies) + cfg, err := b.getConfig(req) + if err != nil { + return nil, err + } + resp.Auth = &logical.Auth{ Policies: policies, Metadata: map[string]string{ @@ -66,6 +72,7 @@ func (b *backend) pathLogin( }, DisplayName: username, LeaseOptions: logical.LeaseOptions{ + TTL: cfg.TTL, Renewable: true, }, } @@ -87,7 +94,25 @@ func (b *backend) pathLoginRenew( return nil, fmt.Errorf("policies have changed, not renewing") } - return framework.LeaseExtend(0, 0, b.System())(req, d) + cfg, err := b.getConfig(req) + if err != nil { + return nil, err + } + + return framework.LeaseExtend(cfg.TTL, cfg.MaxTTL, b.System())(req, d) +} + +func (b *backend) getConfig(req *logical.Request) (*ConfigEntry, error) { + + cfg, err := b.Config(req.Storage) + if err != nil { + return nil, err + } + if cfg == nil { + return nil, errors.New("Okta backend not configured") + } + + return cfg, nil } const pathLoginSyn = ` diff --git a/website/source/docs/auth/okta.html.md b/website/source/docs/auth/okta.html.md index 66839105b914..424cf5aae121 100644 --- a/website/source/docs/auth/okta.html.md +++ b/website/source/docs/auth/okta.html.md @@ -90,6 +90,10 @@ Configuration is written to `auth/okta/config`. * `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 * `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) +* `ttl` (string, optional) - 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) Use `vault path-help` for more details.