Files
teleport/lib/auth/state.go
T
2023-12-01 17:48:14 +00:00

272 lines
8.5 KiB
Go

/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package auth
import (
"context"
"encoding/json"
"strings"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/utils"
)
// stateBackend implements abstraction over local or remote storage backend methods
// required for Identity/State storage.
// As in backend.Backend, Item keys are assumed to be valid UTF8, which may be enforced by the
// various Backend implementations.
type stateBackend interface {
// Create creates item if it does not exist
Create(ctx context.Context, i backend.Item) (*backend.Lease, error)
// Put puts value into backend (creates if it does not
// exists, updates it otherwise)
Put(ctx context.Context, i backend.Item) (*backend.Lease, error)
// Get returns a single item or not found error
Get(ctx context.Context, key []byte) (*backend.Item, error)
}
// ProcessStorage is a backend for local process state,
// it helps to manage rotation for certificate authorities
// and keeps local process credentials - x509 and SSH certs and keys.
type ProcessStorage struct {
// BackendStorage is the SQLite backend used for operations unrelated to storing/reading identities and states.
BackendStorage backend.Backend
// stateStorage is the backend to store agents' identities and states.
// it is not required to close stateBackend storage because it's either the same as BackendStorage or it is Kubernetes
// which does not require any close method
stateStorage stateBackend
}
// Close closes all resources used by process storage backend.
func (p *ProcessStorage) Close() error {
// we do not need to close stateBackend storage because it's either the same as backend or it's kubernetes
// which does not require any close method
return p.BackendStorage.Close()
}
const (
// IdentityCurrent is a name for the identity credentials that are
// currently used by the process.
IdentityCurrent = "current"
// IdentityReplacement is a name for the identity credentials that are
// replacing current identity credentials during CA rotation.
IdentityReplacement = "replacement"
// stateName is an internal resource object name
stateName = "state"
// statesPrefix is a key prefix for object states
statesPrefix = "states"
// idsPrefix is a key prefix for identities
idsPrefix = "ids"
)
// GetState reads rotation state from disk.
func (p *ProcessStorage) GetState(ctx context.Context, role types.SystemRole) (*StateV2, error) {
item, err := p.stateStorage.Get(ctx, backend.Key(statesPrefix, strings.ToLower(role.String()), stateName))
if err != nil {
return nil, trace.Wrap(err)
}
var res StateV2
if err := utils.FastUnmarshal(item.Value, &res); err != nil {
return nil, trace.BadParameter(err.Error())
}
if err := res.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return &res, nil
}
// CreateState creates process state if it does not exist yet.
func (p *ProcessStorage) CreateState(role types.SystemRole, state StateV2) error {
if err := state.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
value, err := json.Marshal(state)
if err != nil {
return trace.Wrap(err)
}
item := backend.Item{
Key: backend.Key(statesPrefix, strings.ToLower(role.String()), stateName),
Value: value,
}
_, err = p.stateStorage.Create(context.TODO(), item)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// WriteState writes local cluster state to the backend.
func (p *ProcessStorage) WriteState(role types.SystemRole, state StateV2) error {
if err := state.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
value, err := json.Marshal(state)
if err != nil {
return trace.Wrap(err)
}
item := backend.Item{
Key: backend.Key(statesPrefix, strings.ToLower(role.String()), stateName),
Value: value,
}
_, err = p.stateStorage.Put(context.TODO(), item)
if err != nil {
return trace.Wrap(err)
}
return nil
}
// ReadIdentity reads identity using identity name and role.
func (p *ProcessStorage) ReadIdentity(name string, role types.SystemRole) (*Identity, error) {
if name == "" {
return nil, trace.BadParameter("missing parameter name")
}
item, err := p.stateStorage.Get(context.TODO(), backend.Key(idsPrefix, strings.ToLower(role.String()), name))
if err != nil {
return nil, trace.Wrap(err)
}
var res IdentityV2
if err := utils.FastUnmarshal(item.Value, &res); err != nil {
return nil, trace.BadParameter(err.Error())
}
if err := res.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return ReadIdentityFromKeyPair(res.Spec.Key, &proto.Certs{
SSH: res.Spec.SSHCert,
TLS: res.Spec.TLSCert,
TLSCACerts: res.Spec.TLSCACerts,
SSHCACerts: res.Spec.SSHCACerts,
})
}
// WriteIdentity writes identity to the backend.
func (p *ProcessStorage) WriteIdentity(name string, id Identity) error {
res := IdentityV2{
ResourceHeader: types.ResourceHeader{
Kind: types.KindIdentity,
Version: types.V2,
Metadata: types.Metadata{
Name: name,
},
},
Spec: IdentitySpecV2{
Key: id.KeyBytes,
SSHCert: id.CertBytes,
TLSCert: id.TLSCertBytes,
TLSCACerts: id.TLSCACertsBytes,
SSHCACerts: id.SSHCACertBytes,
},
}
if err := res.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
value, err := json.Marshal(res)
if err != nil {
return trace.Wrap(err)
}
item := backend.Item{
Key: backend.Key(idsPrefix, strings.ToLower(id.ID.Role.String()), name),
Value: value,
}
_, err = p.stateStorage.Put(context.TODO(), item)
return trace.Wrap(err)
}
// StateV2 is a local process state.
type StateV2 struct {
// ResourceHeader is a common resource header.
types.ResourceHeader
// Spec is a process spec.
Spec StateSpecV2 `json:"spec"`
}
// CheckAndSetDefaults checks and sets defaults values.
func (s *StateV2) CheckAndSetDefaults() error {
s.Kind = types.KindState
s.Version = types.V2
// for state resource name does not matter
if s.Metadata.Name == "" {
s.Metadata.Name = stateName
}
if err := s.Metadata.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
return nil
}
// StateSpecV2 is a state spec.
type StateSpecV2 struct {
// Rotation holds local process rotation state.
Rotation types.Rotation `json:"rotation"`
}
// IdentityV2 specifies local host identity.
type IdentityV2 struct {
// ResourceHeader is a common resource header.
types.ResourceHeader
// Spec is the identity spec.
Spec IdentitySpecV2 `json:"spec"`
}
// CheckAndSetDefaults checks and sets defaults values.
func (s *IdentityV2) CheckAndSetDefaults() error {
s.Kind = types.KindIdentity
s.Version = types.V2
if err := s.Metadata.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
if len(s.Spec.Key) == 0 {
return trace.BadParameter("missing parameter Key")
}
if len(s.Spec.SSHCert) == 0 {
return trace.BadParameter("missing parameter SSHCert")
}
if len(s.Spec.TLSCert) == 0 {
return trace.BadParameter("missing parameter TLSCert")
}
if len(s.Spec.TLSCACerts) == 0 {
return trace.BadParameter("missing parameter TLSCACerts")
}
if len(s.Spec.SSHCACerts) == 0 {
return trace.BadParameter("missing parameter SSH CA bytes")
}
return nil
}
// IdentitySpecV2 specifies credentials used by local process.
type IdentitySpecV2 struct {
// Key is a PEM encoded private key.
Key []byte `json:"key,omitempty"`
// SSHCert is a PEM encoded SSH host cert.
SSHCert []byte `json:"ssh_cert,omitempty"`
// TLSCert is a PEM encoded x509 client certificate.
TLSCert []byte `json:"tls_cert,omitempty"`
// TLSCACert is a list of PEM encoded x509 certificate of the
// certificate authority of the cluster.
TLSCACerts [][]byte `json:"tls_ca_certs,omitempty"`
// SSHCACerts is a list of SSH certificate authorities encoded in the
// authorized_keys format.
SSHCACerts [][]byte `json:"ssh_ca_certs,omitempty"`
}