// TestCoreWithSeal returns a pure in-memory, uninitialized core with the // specified seal for testing. func TestCoreWithSeal(t *testing.T, testSeal Seal) *Core { noopAudits := map[string]audit.Factory{ "noop": func(config *audit.BackendConfig) (audit.Backend, error) { view := &logical.InmemStorage{} view.Put(&logical.StorageEntry{ Key: "salt", Value: []byte("foo"), }) var err error config.Salt, err = salt.NewSalt(view, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { t.Fatalf("error getting new salt: %v", err) } return &noopAudit{ Config: config, }, nil }, } noopBackends := make(map[string]logical.Factory) noopBackends["noop"] = func(config *logical.BackendConfig) (logical.Backend, error) { b := new(framework.Backend) b.Setup(config) return b, nil } noopBackends["http"] = func(config *logical.BackendConfig) (logical.Backend, error) { return new(rawHTTP), nil } logicalBackends := make(map[string]logical.Factory) for backendName, backendFactory := range noopBackends { logicalBackends[backendName] = backendFactory } logicalBackends["generic"] = LeasedPassthroughBackendFactory for backendName, backendFactory := range testLogicalBackends { logicalBackends[backendName] = backendFactory } logger := log.New(os.Stderr, "", log.LstdFlags) physicalBackend := physical.NewInmem(logger) conf := &CoreConfig{ Physical: physicalBackend, AuditBackends: noopAudits, LogicalBackends: logicalBackends, CredentialBackends: noopBackends, DisableMlock: true, Logger: logger, } if testSeal != nil { conf.Seal = testSeal } c, err := NewCore(conf) if err != nil { t.Fatalf("err: %s", err) } return c }
func TestAuditFile_fileModeNew(t *testing.T) { salter, _ := salt.NewSalt(nil, nil) modeStr := "0777" mode, err := strconv.ParseUint(modeStr, 8, 32) path, err := ioutil.TempDir("", "vault-test_audit_file-file_mode_new") defer os.RemoveAll(path) file := filepath.Join(path, "auditTest.txt") config := map[string]string{ "path": file, "mode": modeStr, } _, err = Factory(&audit.BackendConfig{ Salt: salter, Config: config, }) if err != nil { t.Fatal(err) } info, err := os.Stat(file) if err != nil { t.Fatalf("Cannot retrieve file mode from `Stat`") } if info.Mode() != os.FileMode(mode) { t.Fatalf("File mode does not match.") } }
func TestFormatJSON_formatRequest(t *testing.T) { cases := map[string]struct { Auth *logical.Auth Req *logical.Request Err error Result string }{ "auth, request": { &logical.Auth{ClientToken: "foo", Policies: []string{"root"}}, &logical.Request{ Operation: logical.UpdateOperation, Path: "/foo", Connection: &logical.Connection{ RemoteAddr: "127.0.0.1", }, WrapTTL: 60 * time.Second, }, errors.New("this is an error"), testFormatJSONReqBasicStr, }, } for name, tc := range cases { var buf bytes.Buffer formatter := AuditFormatter{ AuditFormatWriter: &JSONFormatWriter{}, } salter, _ := salt.NewSalt(nil, nil) config := FormatterConfig{ Salt: salter, } if err := formatter.FormatRequest(&buf, config, tc.Auth, tc.Req, tc.Err); err != nil { t.Fatalf("bad: %s\nerr: %s", name, err) } var expectedjson = new(AuditRequestEntry) if err := jsonutil.DecodeJSON([]byte(tc.Result), &expectedjson); err != nil { t.Fatalf("bad json: %s", err) } var actualjson = new(AuditRequestEntry) if err := jsonutil.DecodeJSON([]byte(buf.String()), &actualjson); err != nil { t.Fatalf("bad json: %s", err) } expectedjson.Time = actualjson.Time expectedBytes, err := json.Marshal(expectedjson) if err != nil { t.Fatalf("unable to marshal json: %s", err) } if strings.TrimSpace(buf.String()) != string(expectedBytes) { t.Fatalf( "bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'", name, buf.String(), string(expectedBytes)) } } }
func TestSystemBackend_auditHash(t *testing.T) { c, b, _ := testCoreSystemBackend(t) c.auditBackends["noop"] = func(config *audit.BackendConfig) (audit.Backend, error) { view := &logical.InmemStorage{} view.Put(&logical.StorageEntry{ Key: "salt", Value: []byte("foo"), }) var err error config.Salt, err = salt.NewSalt(view, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { t.Fatalf("error getting new salt: %v", err) } return &NoopAudit{ Config: config, }, nil } req := logical.TestRequest(t, logical.UpdateOperation, "audit/foo") req.Data["type"] = "noop" resp, err := b.HandleRequest(req) if err != nil { t.Fatalf("err: %v", err) } if resp != nil { t.Fatalf("bad: %v", resp) } req = logical.TestRequest(t, logical.UpdateOperation, "audit-hash/foo") req.Data["input"] = "bar" resp, err = b.HandleRequest(req) if err != nil { t.Fatalf("err: %v", err) } if resp == nil || resp.Data == nil { t.Fatalf("response or its data was nil") } hash, ok := resp.Data["hash"] if !ok { t.Fatalf("did not get hash back in response, response was %#v", resp.Data) } if hash.(string) != "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317" { t.Fatalf("bad hash back: %s", hash.(string)) } }
func Backend(conf *logical.BackendConfig) (*backend, error) { salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ HashFunc: salt.SHA256Hash, }) if err != nil { return nil, err } b := &backend{ // Setting the periodic func to be run once in an hour. // If there is a real need, this can be made configurable. tidyCooldownPeriod: time.Hour, Salt: salt, EC2ClientsMap: make(map[string]*ec2.EC2), IAMClientsMap: make(map[string]*iam.IAM), } b.Backend = &framework.Backend{ PeriodicFunc: b.periodicFunc, AuthRenew: b.pathLoginRenew, Help: backendHelp, PathsSpecial: &logical.Paths{ Unauthenticated: []string{ "login", }, }, Paths: []*framework.Path{ pathLogin(b), pathListRole(b), pathListRoles(b), pathRole(b), pathRoleTag(b), pathConfigClient(b), pathConfigCertificate(b), pathConfigTidyRoletagBlacklist(b), pathConfigTidyIdentityWhitelist(b), pathListCertificates(b), pathListRoletagBlacklist(b), pathRoletagBlacklist(b), pathTidyRoletagBlacklist(b), pathListIdentityWhitelist(b), pathIdentityWhitelist(b), pathTidyIdentityWhitelist(b), }, } return b, nil }
// newAuditBackend is used to create and configure a new audit backend by name func (c *Core) newAuditBackend(t string, view logical.Storage, conf map[string]string) (audit.Backend, error) { f, ok := c.auditBackends[t] if !ok { return nil, fmt.Errorf("unknown backend type: %s", t) } salter, err := salt.NewSalt(view, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { return nil, fmt.Errorf("[ERR] core: unable to generate salt: %v", err) } return f(&audit.BackendConfig{ Salt: salter, Config: conf, }) }
func TestFormatJSONx_formatRequest(t *testing.T) { cases := map[string]struct { Auth *logical.Auth Req *logical.Request Err error Result string Expected string }{ "auth, request": { &logical.Auth{ClientToken: "foo", Policies: []string{"root"}}, &logical.Request{ Operation: logical.UpdateOperation, Path: "/foo", Connection: &logical.Connection{ RemoteAddr: "127.0.0.1", }, WrapTTL: 60 * time.Second, }, errors.New("this is an error"), "", `<json:object name="auth"><json:string name="accessor"></json:string><json:string name="client_token"></json:string><json:string name="display_name"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:null name="data" /><json:string name="id"></json:string><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`, }, } for name, tc := range cases { var buf bytes.Buffer formatter := AuditFormatter{ AuditFormatWriter: &JSONxFormatWriter{}, } salter, _ := salt.NewSalt(nil, nil) config := FormatterConfig{ Salt: salter, OmitTime: true, } if err := formatter.FormatRequest(&buf, config, tc.Auth, tc.Req, tc.Err); err != nil { t.Fatalf("bad: %s\nerr: %s", name, err) } if strings.TrimSpace(buf.String()) != string(tc.Expected) { t.Fatalf( "bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'", name, strings.TrimSpace(buf.String()), string(tc.Expected)) } } }
func TestHashString(t *testing.T) { inmemStorage := &logical.InmemStorage{} inmemStorage.Put(&logical.StorageEntry{ Key: "salt", Value: []byte("foo"), }) localSalt, err := salt.NewSalt(inmemStorage, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { t.Fatalf("Error instantiating salt: %s", err) } out := HashString(localSalt, "foo") if out != "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a" { t.Fatalf("err: HashString output did not match expected") } }
// newAuditBackend is used to create and configure a new audit backend by name func (c *Core) newAuditBackend(entry *MountEntry, view logical.Storage, conf map[string]string) (audit.Backend, error) { f, ok := c.auditBackends[entry.Type] if !ok { return nil, fmt.Errorf("unknown backend type: %s", entry.Type) } salter, err := salt.NewSalt(view, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { return nil, fmt.Errorf("core: unable to generate salt: %v", err) } be, err := f(&audit.BackendConfig{ Salt: salter, Config: conf, }) if err != nil { return nil, err } switch entry.Type { case "file": key := "audit_file|" + entry.Path c.reloadFuncsLock.Lock() if c.logger.IsDebug() { c.logger.Debug("audit: adding reload function", "path", entry.Path) } c.reloadFuncs[key] = append(c.reloadFuncs[key], func(map[string]string) error { if c.logger.IsInfo() { c.logger.Info("audit: reloading file audit backend", "path", entry.Path) } return be.Reload() }) c.reloadFuncsLock.Unlock() } return be, err }
func Backend(conf *logical.BackendConfig) (*framework.Backend, error) { salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ HashFunc: salt.SHA256Hash, }) if err != nil { return nil, err } var b backend b.salt = salt b.Backend = &framework.Backend{ Help: strings.TrimSpace(backendHelp), PathsSpecial: &logical.Paths{ Root: []string{ "config/*", "keys/*", }, Unauthenticated: []string{ "verify", }, }, Paths: []*framework.Path{ pathConfigZeroAddress(&b), pathKeys(&b), pathListRoles(&b), pathRoles(&b), pathCredsCreate(&b), pathLookup(&b), pathVerify(&b), }, Secrets: []*framework.Secret{ secretDynamicKey(&b), secretOTP(&b), }, } return b.Backend, nil }
func TestAuditFile_fileModeExisting(t *testing.T) { salter, _ := salt.NewSalt(nil, nil) f, err := ioutil.TempFile("", "test") if err != nil { t.Fatalf("Failure to create test file.") } defer os.Remove(f.Name()) err = os.Chmod(f.Name(), 0777) if err != nil { t.Fatalf("Failure to chmod temp file for testing.") } err = f.Close() if err != nil { t.Fatalf("Failure to close temp file for test.") } config := map[string]string{ "path": f.Name(), } _, err = Factory(&audit.BackendConfig{ Salt: salter, Config: config, }) if err != nil { t.Fatal(err) } info, err := os.Stat(f.Name()) if err != nil { t.Fatalf("cannot retrieve file mode from `Stat`") } if info.Mode() != os.FileMode(0600) { t.Fatalf("File mode does not match.") } }
func TestHash(t *testing.T) { now := time.Now().UTC() cases := []struct { Input interface{} Output interface{} }{ { &logical.Auth{ClientToken: "foo"}, &logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"}, }, { &logical.Request{ Data: map[string]interface{}{ "foo": "bar", }, }, &logical.Request{ Data: map[string]interface{}{ "foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", }, }, }, { &logical.Response{ Data: map[string]interface{}{ "foo": "bar", }, }, &logical.Response{ Data: map[string]interface{}{ "foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", }, }, }, { "foo", "foo", }, { &logical.Auth{ LeaseOptions: logical.LeaseOptions{ TTL: 1 * time.Hour, IssueTime: now, }, ClientToken: "foo", }, &logical.Auth{ LeaseOptions: logical.LeaseOptions{ TTL: 1 * time.Hour, IssueTime: now, }, ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a", }, }, } inmemStorage := &logical.InmemStorage{} inmemStorage.Put(&logical.StorageEntry{ Key: "salt", Value: []byte("foo"), }) localSalt, err := salt.NewSalt(inmemStorage, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { t.Fatalf("Error instantiating salt: %s", err) } for _, tc := range cases { input := fmt.Sprintf("%#v", tc.Input) if err := Hash(localSalt, tc.Input); err != nil { t.Fatalf("err: %s\n\n%s", err, input) } if !reflect.DeepEqual(tc.Input, tc.Output) { t.Fatalf("bad:\n\n%s\n\n%#v\n\n%#v", input, tc.Input, tc.Output) } } }
func TestPathMap_Salted(t *testing.T) { storage := new(logical.InmemStorage) salt, err := salt.NewSalt(storage, &salt.Config{ HashFunc: salt.SHA1Hash, }) if err != nil { t.Fatalf("err: %v", err) } p := &PathMap{Name: "foo", Salt: salt} var b logical.Backend = &Backend{Paths: p.Paths()} // Write via HTTP _, err = b.HandleRequest(&logical.Request{ Operation: logical.WriteOperation, Path: "map/foo/a", Data: map[string]interface{}{ "value": "bar", }, Storage: storage, }) if err != nil { t.Fatalf("bad: %#v", err) } // Non-salted version should not be there out, err := storage.Get("struct/map/foo/a") if err != nil { t.Fatalf("err: %v", err) } if out != nil { t.Fatalf("non-salted key found") } // Ensure the path is salted expect := salt.SaltID("a") out, err = storage.Get("struct/map/foo/" + expect) if err != nil { t.Fatalf("err: %v", err) } if out == nil { t.Fatalf("missing salted key") } // Read via HTTP resp, err := b.HandleRequest(&logical.Request{ Operation: logical.ReadOperation, Path: "map/foo/a", Storage: storage, }) if err != nil { t.Fatalf("bad: %#v", err) } if resp.Data["value"] != "bar" { t.Fatalf("bad: %#v", resp) } // Read via API v, err := p.Get(storage, "a") if err != nil { t.Fatalf("bad: %#v", err) } if v["value"] != "bar" { t.Fatalf("bad: %#v", v) } // Read via API with other casing v, err = p.Get(storage, "A") if err != nil { t.Fatalf("bad: %#v", err) } if v["value"] != "bar" { t.Fatalf("bad: %#v", v) } // Verify List keys, err := p.List(storage, "") if err != nil { t.Fatalf("bad: %#v", err) } if len(keys) != 1 || keys[0] != expect { t.Fatalf("bad: %#v", keys) } // Delete via HTTP resp, err = b.HandleRequest(&logical.Request{ Operation: logical.DeleteOperation, Path: "map/foo/a", Storage: storage, }) if err != nil { t.Fatalf("bad: %#v", err) } if resp != nil { t.Fatalf("bad: %#v", resp) } // Re-read via HTTP resp, err = b.HandleRequest(&logical.Request{ Operation: logical.ReadOperation, Path: "map/foo/a", Storage: storage, }) if err != nil { t.Fatalf("bad: %#v", err) } if _, ok := resp.Data["value"]; ok { t.Fatalf("bad: %#v", resp) } // Re-read via API v, err = p.Get(storage, "a") if err != nil { t.Fatalf("bad: %#v", err) } if v != nil { t.Fatalf("bad: %#v", v) } }
// NewTokenStore is used to construct a token store that is // backed by the given barrier view. func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) { // Create a sub-view view := c.systemBarrierView.SubView(tokenSubPath) // Initialize the store t := &TokenStore{ view: view, } // Setup the salt salt, err := salt.NewSalt(view, nil) if err != nil { return nil, err } t.salt = salt // Setup the framework endpoints t.Backend = &framework.Backend{ // Allow a token lease to be extended indefinitely, but each time for only // as much as the original lease allowed for. If the lease has a 1 hour expiration, // it can only be extended up to another hour each time this means. AuthRenew: framework.LeaseExtend(0, 0, true), PathsSpecial: &logical.Paths{ Root: []string{ "revoke-prefix/*", "revoke-orphan/*", }, }, Paths: []*framework.Path{ &framework.Path{ Pattern: "create$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleCreate, }, HelpSynopsis: strings.TrimSpace(tokenCreateHelp), HelpDescription: strings.TrimSpace(tokenCreateHelp), }, &framework.Path{ Pattern: "lookup/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookup, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "lookup-self$", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookup, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "revoke-self", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleRevokeSelf, }, HelpSynopsis: strings.TrimSpace(tokenRevokeSelfHelp), HelpDescription: strings.TrimSpace(tokenRevokeSelfHelp), }, &framework.Path{ Pattern: "revoke/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleRevokeTree, }, HelpSynopsis: strings.TrimSpace(tokenRevokeHelp), HelpDescription: strings.TrimSpace(tokenRevokeHelp), }, &framework.Path{ Pattern: "revoke-orphan/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleRevokeOrphan, }, HelpSynopsis: strings.TrimSpace(tokenRevokeOrphanHelp), HelpDescription: strings.TrimSpace(tokenRevokeOrphanHelp), }, &framework.Path{ Pattern: "revoke-prefix/(?P<prefix>.+)", Fields: map[string]*framework.FieldSchema{ "prefix": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token source prefix to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleRevokePrefix, }, HelpSynopsis: strings.TrimSpace(tokenRevokePrefixHelp), HelpDescription: strings.TrimSpace(tokenRevokePrefixHelp), }, &framework.Path{ Pattern: "renew/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew", }, "increment": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Description: "The desired increment in seconds to the token expiration", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.WriteOperation: t.handleRenew, }, HelpSynopsis: strings.TrimSpace(tokenRenewHelp), HelpDescription: strings.TrimSpace(tokenRenewHelp), }, }, } t.Backend.Setup(config) return t, nil }
// NewTokenStore is used to construct a token store that is // backed by the given barrier view. func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) { // Create a sub-view view := c.systemBarrierView.SubView(tokenSubPath) // Initialize the store t := &TokenStore{ view: view, } if c.policyStore != nil { t.policyLookupFunc = c.policyStore.GetPolicy } // Setup the salt salt, err := salt.NewSalt(view, &salt.Config{ HashFunc: salt.SHA1Hash, }) if err != nil { return nil, err } t.salt = salt // Setup the framework endpoints t.Backend = &framework.Backend{ AuthRenew: t.authRenew, PathsSpecial: &logical.Paths{ Root: []string{ "revoke-orphan/*", }, }, Paths: []*framework.Path{ &framework.Path{ Pattern: "roles/?$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ListOperation: t.tokenStoreRoleList, }, HelpSynopsis: tokenListRolesHelp, HelpDescription: tokenListRolesHelp, }, &framework.Path{ Pattern: "roles/" + framework.GenericNameRegex("role_name"), Fields: map[string]*framework.FieldSchema{ "role_name": &framework.FieldSchema{ Type: framework.TypeString, Description: "Name of the role", }, "allowed_policies": &framework.FieldSchema{ Type: framework.TypeString, Default: "", Description: tokenAllowedPoliciesHelp, }, "orphan": &framework.FieldSchema{ Type: framework.TypeBool, Default: false, Description: tokenOrphanHelp, }, "period": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Default: 0, Description: tokenPeriodHelp, }, "path_suffix": &framework.FieldSchema{ Type: framework.TypeString, Default: "", Description: tokenPathSuffixHelp + pathSuffixSanitize.String(), }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.tokenStoreRoleRead, logical.CreateOperation: t.tokenStoreRoleCreateUpdate, logical.UpdateOperation: t.tokenStoreRoleCreateUpdate, logical.DeleteOperation: t.tokenStoreRoleDelete, }, ExistenceCheck: t.tokenStoreRoleExistenceCheck, HelpSynopsis: tokenPathRolesHelp, HelpDescription: tokenPathRolesHelp, }, &framework.Path{ Pattern: "create-orphan$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleCreateOrphan, }, HelpSynopsis: strings.TrimSpace(tokenCreateOrphanHelp), HelpDescription: strings.TrimSpace(tokenCreateOrphanHelp), }, &framework.Path{ Pattern: "create/" + framework.GenericNameRegex("role_name"), Fields: map[string]*framework.FieldSchema{ "role_name": &framework.FieldSchema{ Type: framework.TypeString, Description: "Name of the role", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleCreateAgainstRole, }, HelpSynopsis: strings.TrimSpace(tokenCreateRoleHelp), HelpDescription: strings.TrimSpace(tokenCreateRoleHelp), }, &framework.Path{ Pattern: "create$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleCreate, }, HelpSynopsis: strings.TrimSpace(tokenCreateHelp), HelpDescription: strings.TrimSpace(tokenCreateHelp), }, &framework.Path{ Pattern: "lookup" + framework.OptionalParamRegex("urltoken"), Fields: map[string]*framework.FieldSchema{ "urltoken": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup (GET/POST URL parameter)", }, "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup (POST request body)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookup, logical.UpdateOperation: t.handleLookup, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "lookup-accessor" + framework.OptionalParamRegex("urlaccessor"), Fields: map[string]*framework.FieldSchema{ "urlaccessor": &framework.FieldSchema{ Type: framework.TypeString, Description: "Accessor of the token to look up (URL parameter)", }, "accessor": &framework.FieldSchema{ Type: framework.TypeString, Description: "Accessor of the token to look up (request body)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleUpdateLookupAccessor, }, HelpSynopsis: strings.TrimSpace(tokenLookupAccessorHelp), HelpDescription: strings.TrimSpace(tokenLookupAccessorHelp), }, &framework.Path{ Pattern: "lookup-self$", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to look up (unused)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookupSelf, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "revoke-accessor" + framework.OptionalParamRegex("urlaccessor"), Fields: map[string]*framework.FieldSchema{ "urlaccessor": &framework.FieldSchema{ Type: framework.TypeString, Description: "Accessor of the token (in URL)", }, "accessor": &framework.FieldSchema{ Type: framework.TypeString, Description: "Accessor of the token (request body)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleUpdateRevokeAccessor, }, HelpSynopsis: strings.TrimSpace(tokenRevokeAccessorHelp), HelpDescription: strings.TrimSpace(tokenRevokeAccessorHelp), }, &framework.Path{ Pattern: "revoke-self$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeSelf, }, HelpSynopsis: strings.TrimSpace(tokenRevokeSelfHelp), HelpDescription: strings.TrimSpace(tokenRevokeSelfHelp), }, &framework.Path{ Pattern: "revoke" + framework.OptionalParamRegex("urltoken"), Fields: map[string]*framework.FieldSchema{ "urltoken": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke (in URL)", }, "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke (request body)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeTree, }, HelpSynopsis: strings.TrimSpace(tokenRevokeHelp), HelpDescription: strings.TrimSpace(tokenRevokeHelp), }, &framework.Path{ Pattern: "revoke-orphan" + framework.OptionalParamRegex("urltoken"), Fields: map[string]*framework.FieldSchema{ "urltoken": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke (in URL)", }, "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke (request body)", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeOrphan, }, HelpSynopsis: strings.TrimSpace(tokenRevokeOrphanHelp), HelpDescription: strings.TrimSpace(tokenRevokeOrphanHelp), }, &framework.Path{ Pattern: "renew-self$", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew (unused)", }, "increment": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Default: 0, Description: "The desired increment in seconds to the token expiration", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRenewSelf, }, HelpSynopsis: strings.TrimSpace(tokenRenewSelfHelp), HelpDescription: strings.TrimSpace(tokenRenewSelfHelp), }, &framework.Path{ Pattern: "renew" + framework.OptionalParamRegex("urltoken"), Fields: map[string]*framework.FieldSchema{ "urltoken": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew (in URL)", }, "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew (request body)", }, "increment": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Default: 0, Description: "The desired increment in seconds to the token expiration", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRenew, }, HelpSynopsis: strings.TrimSpace(tokenRenewHelp), HelpDescription: strings.TrimSpace(tokenRenewHelp), }, }, } t.Backend.Setup(config) return t, nil }
func Backend(conf *logical.BackendConfig) (*backend, error) { // Initialize the salt salt, err := salt.NewSalt(conf.StorageView, &salt.Config{ HashFunc: salt.SHA256Hash, }) if err != nil { return nil, err } // Create a backend object b := &backend{ // Set the salt object for the backend salt: salt, // Create the map of locks to modify the registered roles roleLocksMap: make(map[string]*sync.RWMutex, 257), // Create the map of locks to modify the generated RoleIDs roleIDLocksMap: make(map[string]*sync.RWMutex, 257), // Create the map of locks to modify the generated SecretIDs secretIDLocksMap: make(map[string]*sync.RWMutex, 257), // Create the map of locks to modify the generated SecretIDAccessors secretIDAccessorLocksMap: make(map[string]*sync.RWMutex, 257), } // Create 256 locks each for managing RoleID and SecretIDs. This will avoid // a superfluous number of locks directly proportional to the number of RoleID // and SecretIDs. These locks can be accessed by indexing based on the first two // characters of a randomly generated UUID. if err = locksutil.CreateLocks(b.roleLocksMap, 256); err != nil { return nil, fmt.Errorf("failed to create role locks: %v", err) } if err = locksutil.CreateLocks(b.roleIDLocksMap, 256); err != nil { return nil, fmt.Errorf("failed to create role ID locks: %v", err) } if err = locksutil.CreateLocks(b.secretIDLocksMap, 256); err != nil { return nil, fmt.Errorf("failed to create secret ID locks: %v", err) } if err = locksutil.CreateLocks(b.secretIDAccessorLocksMap, 256); err != nil { return nil, fmt.Errorf("failed to create secret ID accessor locks: %v", err) } // Have an extra lock to use in case the indexing does not result in a lock. // This happens if the indexing value is not beginning with hex characters. // These locks can be used for listing purposes as well. b.roleLocksMap["custom"] = &sync.RWMutex{} b.roleIDLocksMap["custom"] = &sync.RWMutex{} b.secretIDLocksMap["custom"] = &sync.RWMutex{} b.secretIDAccessorLocksMap["custom"] = &sync.RWMutex{} // Attach the paths and secrets that are to be handled by the backend b.Backend = &framework.Backend{ // Register a periodic function that deletes the expired SecretID entries PeriodicFunc: b.periodicFunc, Help: backendHelp, AuthRenew: b.pathLoginRenew, PathsSpecial: &logical.Paths{ Unauthenticated: []string{ "login", }, }, Paths: framework.PathAppend( rolePaths(b), []*framework.Path{ pathLogin(b), pathTidySecretID(b), }, ), } return b, nil }
// NewTokenStore is used to construct a token store that is // backed by the given barrier view. func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error) { // Create a sub-view view := c.systemBarrierView.SubView(tokenSubPath) // Initialize the store t := &TokenStore{ view: view, } if c.policyStore != nil { t.policyLookupFunc = c.policyStore.GetPolicy } // Setup the salt salt, err := salt.NewSalt(view, &salt.Config{ HashFunc: salt.SHA1Hash, }) if err != nil { return nil, err } t.salt = salt // Setup the framework endpoints t.Backend = &framework.Backend{ AuthRenew: t.authRenew, PathsSpecial: &logical.Paths{ Root: []string{ "revoke-prefix/*", "revoke-orphan/*", }, }, Paths: []*framework.Path{ &framework.Path{ Pattern: "create-orphan$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleCreateOrphan, }, HelpSynopsis: strings.TrimSpace(tokenCreateOrphanHelp), HelpDescription: strings.TrimSpace(tokenCreateOrphanHelp), }, &framework.Path{ Pattern: "create$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleCreate, }, HelpSynopsis: strings.TrimSpace(tokenCreateHelp), HelpDescription: strings.TrimSpace(tokenCreateHelp), }, &framework.Path{ Pattern: "lookup/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookup, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "lookup-self$", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to lookup", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: t.handleLookup, }, HelpSynopsis: strings.TrimSpace(tokenLookupHelp), HelpDescription: strings.TrimSpace(tokenLookupHelp), }, &framework.Path{ Pattern: "revoke-self$", Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeSelf, }, HelpSynopsis: strings.TrimSpace(tokenRevokeSelfHelp), HelpDescription: strings.TrimSpace(tokenRevokeSelfHelp), }, &framework.Path{ Pattern: "revoke/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeTree, }, HelpSynopsis: strings.TrimSpace(tokenRevokeHelp), HelpDescription: strings.TrimSpace(tokenRevokeHelp), }, &framework.Path{ Pattern: "revoke-orphan/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokeOrphan, }, HelpSynopsis: strings.TrimSpace(tokenRevokeOrphanHelp), HelpDescription: strings.TrimSpace(tokenRevokeOrphanHelp), }, &framework.Path{ Pattern: "revoke-prefix/(?P<prefix>.+)", Fields: map[string]*framework.FieldSchema{ "prefix": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token source prefix to revoke", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRevokePrefix, }, HelpSynopsis: strings.TrimSpace(tokenRevokePrefixHelp), HelpDescription: strings.TrimSpace(tokenRevokePrefixHelp), }, &framework.Path{ Pattern: "renew-self$", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew", }, "increment": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Description: "The desired increment in seconds to the token expiration", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRenewSelf, }, HelpSynopsis: strings.TrimSpace(tokenRenewSelfHelp), HelpDescription: strings.TrimSpace(tokenRenewSelfHelp), }, &framework.Path{ Pattern: "renew/(?P<token>.+)", Fields: map[string]*framework.FieldSchema{ "token": &framework.FieldSchema{ Type: framework.TypeString, Description: "Token to renew", }, "increment": &framework.FieldSchema{ Type: framework.TypeDurationSecond, Description: "The desired increment in seconds to the token expiration", }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.UpdateOperation: t.handleRenew, }, HelpSynopsis: strings.TrimSpace(tokenRenewHelp), HelpDescription: strings.TrimSpace(tokenRenewHelp), }, }, } t.Backend.Setup(config) return t, nil }
func Backend(conf *logical.BackendConfig) (*framework.Backend, error) { // Initialize the salt salt, err := salt.NewSalt(conf.View, nil) if err != nil { return nil, err } var b backend b.Salt = salt b.MapAppId = &framework.PolicyMap{ PathMap: framework.PathMap{ Name: "app-id", Salt: salt, Schema: map[string]*framework.FieldSchema{ "display_name": &framework.FieldSchema{ Type: framework.TypeString, Description: "A name to map to this app ID for logs.", }, "value": &framework.FieldSchema{ Type: framework.TypeString, Description: "Policies for the app ID.", }, }, }, DefaultKey: "default", } b.MapUserId = &framework.PathMap{ Name: "user-id", Salt: salt, Schema: map[string]*framework.FieldSchema{ "cidr_block": &framework.FieldSchema{ Type: framework.TypeString, Description: "If not blank, restricts auth by this CIDR block", }, "value": &framework.FieldSchema{ Type: framework.TypeString, Description: "App IDs that this user associates with.", }, }, } b.Backend = &framework.Backend{ Help: backendHelp, PathsSpecial: &logical.Paths{ Unauthenticated: []string{ "login", }, }, Paths: framework.PathAppend([]*framework.Path{ pathLogin(&b), }, b.MapAppId.Paths(), b.MapUserId.Paths(), ), } // Since the salt is new in 0.2, we need to handle this by migrating // any existing keys to use the salt. We can deprecate this eventually, // but for now we want a smooth upgrade experience by automatically // upgrading to use salting. if salt.DidGenerate() { if err := b.upgradeToSalted(conf.View); err != nil { return nil, err } } return b.Backend, nil }
func TestHash(t *testing.T) { now := time.Now() cases := []struct { Input interface{} Output interface{} }{ { &logical.Auth{ClientToken: "foo"}, &logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"}, }, { &logical.Request{ Data: map[string]interface{}{ "foo": "bar", "private_key_type": certutil.PrivateKeyType("rsa"), }, }, &logical.Request{ Data: map[string]interface{}{ "foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", "private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1", }, }, }, { &logical.Response{ Data: map[string]interface{}{ "foo": "bar", }, WrapInfo: &logical.WrapInfo{ TTL: 60, Token: "bar", CreationTime: now, WrappedAccessor: "bar", }, }, &logical.Response{ Data: map[string]interface{}{ "foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", }, WrapInfo: &logical.WrapInfo{ TTL: 60, Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", CreationTime: now, WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", }, }, }, { "foo", "foo", }, { &logical.Auth{ LeaseOptions: logical.LeaseOptions{ TTL: 1 * time.Hour, IssueTime: now, }, ClientToken: "foo", }, &logical.Auth{ LeaseOptions: logical.LeaseOptions{ TTL: 1 * time.Hour, IssueTime: now, }, ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a", }, }, } inmemStorage := &logical.InmemStorage{} inmemStorage.Put(&logical.StorageEntry{ Key: "salt", Value: []byte("foo"), }) localSalt, err := salt.NewSalt(inmemStorage, &salt.Config{ HMAC: sha256.New, HMACType: "hmac-sha256", }) if err != nil { t.Fatalf("Error instantiating salt: %s", err) } for _, tc := range cases { input := fmt.Sprintf("%#v", tc.Input) if err := Hash(localSalt, tc.Input); err != nil { t.Fatalf("err: %s\n\n%s", err, input) } if !reflect.DeepEqual(tc.Input, tc.Output) { t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output\n%#v", input, tc.Input, tc.Output) } } }