1package approle
2
3import (
4	"context"
5	"sync"
6	"time"
7
8	"github.com/hashicorp/vault/sdk/framework"
9	"github.com/hashicorp/vault/sdk/helper/consts"
10	"github.com/hashicorp/vault/sdk/helper/locksutil"
11	"github.com/hashicorp/vault/sdk/helper/salt"
12	"github.com/hashicorp/vault/sdk/logical"
13)
14
15const (
16	secretIDPrefix              = "secret_id/"
17	secretIDLocalPrefix         = "secret_id_local/"
18	secretIDAccessorPrefix      = "accessor/"
19	secretIDAccessorLocalPrefix = "accessor_local/"
20)
21
22type backend struct {
23	*framework.Backend
24
25	// The salt value to be used by the information to be accessed only
26	// by this backend.
27	salt      *salt.Salt
28	saltMutex sync.RWMutex
29
30	// The view to use when creating the salt
31	view logical.Storage
32
33	// Guard to clean-up the expired SecretID entries
34	tidySecretIDCASGuard *uint32
35
36	// Locks to make changes to role entries. These will be initialized to a
37	// predefined number of locks when the backend is created, and will be
38	// indexed based on salted role names.
39	roleLocks []*locksutil.LockEntry
40
41	// Locks to make changes to the storage entries of RoleIDs generated. These
42	// will be initialized to a predefined number of locks when the backend is
43	// created, and will be indexed based on the salted RoleIDs.
44	roleIDLocks []*locksutil.LockEntry
45
46	// Locks to make changes to the storage entries of SecretIDs generated.
47	// These will be initialized to a predefined number of locks when the
48	// backend is created, and will be indexed based on the HMAC-ed SecretIDs.
49	secretIDLocks []*locksutil.LockEntry
50
51	// Locks to make changes to the storage entries of SecretIDAccessors
52	// generated. These will be initialized to a predefined number of locks
53	// when the backend is created, and will be indexed based on the
54	// SecretIDAccessors itself.
55	secretIDAccessorLocks []*locksutil.LockEntry
56
57	// secretIDListingLock is a dedicated lock for listing SecretIDAccessors
58	// for all the SecretIDs issued against an approle
59	secretIDListingLock sync.RWMutex
60
61	testTidyDelay time.Duration
62}
63
64func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
65	b, err := Backend(conf)
66	if err != nil {
67		return nil, err
68	}
69	if err := b.Setup(ctx, conf); err != nil {
70		return nil, err
71	}
72	return b, nil
73}
74
75func Backend(conf *logical.BackendConfig) (*backend, error) {
76	// Create a backend object
77	b := &backend{
78		view: conf.StorageView,
79
80		// Create locks to modify the registered roles
81		roleLocks: locksutil.CreateLocks(),
82
83		// Create locks to modify the generated RoleIDs
84		roleIDLocks: locksutil.CreateLocks(),
85
86		// Create locks to modify the generated SecretIDs
87		secretIDLocks: locksutil.CreateLocks(),
88
89		// Create locks to modify the generated SecretIDAccessors
90		secretIDAccessorLocks: locksutil.CreateLocks(),
91
92		tidySecretIDCASGuard: new(uint32),
93	}
94
95	// Attach the paths and secrets that are to be handled by the backend
96	b.Backend = &framework.Backend{
97		// Register a periodic function that deletes the expired SecretID entries
98		PeriodicFunc: b.periodicFunc,
99		Help:         backendHelp,
100		AuthRenew:    b.pathLoginRenew,
101		PathsSpecial: &logical.Paths{
102			Unauthenticated: []string{
103				"login",
104			},
105			LocalStorage: []string{
106				secretIDLocalPrefix,
107				secretIDAccessorLocalPrefix,
108			},
109		},
110		Paths: framework.PathAppend(
111			rolePaths(b),
112			[]*framework.Path{
113				pathLogin(b),
114				pathTidySecretID(b),
115			},
116		),
117		Invalidate:  b.invalidate,
118		BackendType: logical.TypeCredential,
119	}
120	return b, nil
121}
122
123func (b *backend) Salt(ctx context.Context) (*salt.Salt, error) {
124	b.saltMutex.RLock()
125	if b.salt != nil {
126		defer b.saltMutex.RUnlock()
127		return b.salt, nil
128	}
129	b.saltMutex.RUnlock()
130	b.saltMutex.Lock()
131	defer b.saltMutex.Unlock()
132	if b.salt != nil {
133		return b.salt, nil
134	}
135	salt, err := salt.NewSalt(ctx, b.view, &salt.Config{
136		HashFunc: salt.SHA256Hash,
137		Location: salt.DefaultLocation,
138	})
139	if err != nil {
140		return nil, err
141	}
142	b.salt = salt
143	return salt, nil
144}
145
146func (b *backend) invalidate(_ context.Context, key string) {
147	switch key {
148	case salt.DefaultLocation:
149		b.saltMutex.Lock()
150		defer b.saltMutex.Unlock()
151		b.salt = nil
152	}
153}
154
155// periodicFunc of the backend will be invoked once a minute by the RollbackManager.
156// RoleRole backend utilizes this function to delete expired SecretID entries.
157// This could mean that the SecretID may live in the backend upto 1 min after its
158// expiration. The deletion of SecretIDs are not security sensitive and it is okay
159// to delay the removal of SecretIDs by a minute.
160func (b *backend) periodicFunc(ctx context.Context, req *logical.Request) error {
161	// Initiate clean-up of expired SecretID entries
162	if b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary|consts.ReplicationPerformanceStandby) {
163		b.tidySecretID(ctx, req)
164	}
165	return nil
166}
167
168const backendHelp = `
169Any registered Role can authenticate itself with Vault. The credentials
170depends on the constraints that are set on the Role. One common required
171credential is the 'role_id' which is a unique identifier of the Role.
172It can be retrieved from the 'role/<appname>/role-id' endpoint.
173
174The default constraint configuration is 'bind_secret_id', which requires
175the credential 'secret_id' to be presented during login. Refer to the
176documentation for other types of constraints.`
177