// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package cachememdb import ( "context" "encoding/json" "net/http" "sync" "time" ) // Index holds the response to be cached along with multiple other values that // serve as pointers to refer back to this index. type Index struct { // ID is a value that uniquely represents the request held by this // index. This is computed by serializing and hashing the response object. // Required: true, Unique: true ID string // Token is the token that fetched the response held by this index // Required: true, Unique: true Token string // Tokens is a set of tokens that can access this cached response, // which is used for static secret caching, and enabling multiple // tokens to be able to access the same cache entry for static secrets. // Implemented as a map so that all values are unique. // Required: false, Unique: false Tokens map[string]struct{} // TokenParent is the parent token of the token held by this index // Required: false, Unique: false TokenParent string // TokenAccessor is the accessor of the token being cached in this index // Required: true, Unique: true TokenAccessor string // Namespace is the namespace that was provided in the request path as the // Vault namespace to query Namespace string // RequestPath is the path of the request that resulted in the response // held by this index. // For dynamic secrets, this will be the actual path sent to the request, // e.g. /v1/foo/bar (which will not include the namespace if it was included // in the headers). // For static secrets, this will be the canonical path to the secret (i.e. // after calling getStaticSecretPathFromRequest--see its godocs for more // information). // Required: true, Unique: false RequestPath string // Versions are the versions of the secret for KVv2 static secrets only. This is // a map of version to response, where version is the version number and response is the // serialized cached response for that secret version. // We could have chosen to put index.Response as Versions[0], but opted not to for consistency, // and also to elevate the fact that the current version/representation of the path being // cached here is stored there, not here. // Required: false, Unique: false Versions map[int][]byte // Lease is the identifier of the lease in Vault, that belongs to the // response held by this index. // Required: false, Unique: true Lease string // LeaseToken is the identifier of the token that created the lease held by // this index. // Required: false, Unique: false LeaseToken string // Response is the serialized response object that the agent is caching. Response []byte // RenewCtxInfo holds the context and the corresponding cancel func for the // goroutine that manages the renewal of the secret belonging to the // response in this index. RenewCtxInfo *ContextInfo // RequestMethod is the HTTP method of the request RequestMethod string // RequestToken is the token used in the request RequestToken string // RequestHeader is the header used in the request RequestHeader http.Header // LastRenewed is the timestamp of last renewal LastRenewed time.Time // Type is the index type (token, auth-lease, secret-lease, static-secret) Type string // IndexLock is a lock held for some indexes to prevent data // races upon update. IndexLock sync.RWMutex } // CapabilitiesIndex holds the capabilities for cached static secrets. // This type of index does not represent a response. type CapabilitiesIndex struct { // ID is a value that uniquely represents the request held by this // index. This is computed by hashing the token that this capabilities // index represents the capabilities of. // Required: true, Unique: true ID string // Token is the token that fetched the response held by this index // Required: true, Unique: true Token string // ReadablePaths is a set of paths with read capabilities for the given token. // Implemented as a map for uniqueness. The key to the map is a path (such as // `foo/bar` that we've demonstrated we can read. ReadablePaths map[string]struct{} // IndexLock is a lock held for some indexes to prevent data // races upon update. IndexLock sync.RWMutex } type IndexName uint32 const ( // IndexNameID is the ID of the index constructed from the serialized request. IndexNameID = "id" // IndexNameLease is the lease of the index. IndexNameLease = "lease" // IndexNameRequestPath is the request path of the index. IndexNameRequestPath = "request_path" // IndexNameToken is the token of the index. IndexNameToken = "token" // IndexNameTokenAccessor is the token accessor of the index. IndexNameTokenAccessor = "token_accessor" // IndexNameTokenParent is the token parent of the index. IndexNameTokenParent = "token_parent" // IndexNameLeaseToken is the token that created the lease. IndexNameLeaseToken = "lease_token" // CapabilitiesIndexNameID is the ID of the capabilities index. CapabilitiesIndexNameID = "id" ) func validIndexName(indexName string) bool { switch indexName { case IndexNameID: case IndexNameLease: case IndexNameRequestPath: case IndexNameToken: case IndexNameTokenAccessor: case IndexNameTokenParent: case IndexNameLeaseToken: default: return false } return true } func validCapabilitiesIndexName(indexName string) bool { switch indexName { case CapabilitiesIndexNameID: default: return false } return true } type ContextInfo struct { Ctx context.Context CancelFunc context.CancelFunc DoneCh chan struct{} } func NewContextInfo(ctx context.Context) *ContextInfo { if ctx == nil { return nil } ctxInfo := new(ContextInfo) ctxInfo.Ctx, ctxInfo.CancelFunc = context.WithCancel(ctx) ctxInfo.DoneCh = make(chan struct{}) return ctxInfo } // Serialize returns a json marshal'ed Index object, without the RenewCtxInfo func (i Index) Serialize() ([]byte, error) { i.RenewCtxInfo = nil indexBytes, err := json.Marshal(i) if err != nil { return nil, err } return indexBytes, nil } // Deserialize converts json bytes to an Index object // Note: RenewCtxInfo will need to be reconstructed elsewhere. func Deserialize(indexBytes []byte) (*Index, error) { index := new(Index) if err := json.Unmarshal(indexBytes, index); err != nil { return nil, err } return index, nil } // SerializeCapabilitiesIndex returns a json marshal'ed CapabilitiesIndex object func (i CapabilitiesIndex) SerializeCapabilitiesIndex() ([]byte, error) { indexBytes, err := json.Marshal(i) if err != nil { return nil, err } return indexBytes, nil } // DeserializeCapabilitiesIndex converts json bytes to an CapabilitiesIndex object func DeserializeCapabilitiesIndex(indexBytes []byte) (*CapabilitiesIndex, error) { index := new(CapabilitiesIndex) if err := json.Unmarshal(indexBytes, index); err != nil { return nil, err } return index, nil }