* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * Update copyright file headers to BUS-1.1 * Rerun make gen This will pick up the last of the license changes * Revert "Rerun make gen" This reverts commit dbe0968bffd92745d8663c6940debae098395a6b. * Remove copywrite from generation script The current version of copywrite is not sophisticated enough to only apply the copywrite changes to certain subdirectories, so to avoid accidentally relicensing our api and sdk modules, we will stop running the copywrite script until such time that it can do the right thing. * License protobuf API as MPL The protobuf API files are used to generate the SDK, since we want the SDK to be MPL, the protobuf files must also be MPL. This extends to all protobuf files whose sources end up in the SDK directory, including: - internal/proto/controller/api - internal/proto/controller/custom_options - internal/proto/plugin It also includes the generated service directory in internal/gen/controller/api/services, since they are generated from the protobuf sources used for the SDK. * Change website license file name This makes the naming scheme consistent with the rest of the repository. * Add note on licensing to README * Set copywrite license to BUSL * Revert "Add note on licensing to README" This reverts commit 4d865a91a7a4f11605f0dc4609355d7e3c3cfffe. --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
306 lines
9.1 KiB
Go
306 lines
9.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/fatih/structs"
|
|
"github.com/hashicorp/boundary/internal/db/timestamp"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/server/store"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
type (
|
|
WorkerType string
|
|
OperationalState string
|
|
)
|
|
|
|
const (
|
|
UnknownWorkerType WorkerType = "unknown"
|
|
KmsWorkerType WorkerType = "kms"
|
|
PkiWorkerType WorkerType = "pki"
|
|
ActiveOperationalState OperationalState = "active"
|
|
ShutdownOperationalState OperationalState = "shutdown"
|
|
UnknownOperationalState OperationalState = "unknown"
|
|
)
|
|
|
|
func (t WorkerType) Valid() bool {
|
|
switch t {
|
|
case KmsWorkerType, PkiWorkerType:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (t WorkerType) String() string {
|
|
switch t {
|
|
case KmsWorkerType, PkiWorkerType:
|
|
return string(t)
|
|
}
|
|
return string(UnknownWorkerType)
|
|
}
|
|
|
|
type workerAuthWorkerId struct {
|
|
WorkerId string `mapstructure:"worker_id"`
|
|
}
|
|
|
|
func ValidOperationalState(s string) bool {
|
|
switch s {
|
|
case ActiveOperationalState.String(), ShutdownOperationalState.String():
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (t OperationalState) String() string {
|
|
switch t {
|
|
case ActiveOperationalState, ShutdownOperationalState:
|
|
return string(t)
|
|
}
|
|
return string(UnknownOperationalState)
|
|
}
|
|
|
|
// AttachWorkerIdToState accepts a workerId and creates a struct for use with the Nodeenrollment lib
|
|
// This is intended for use in worker authorization; AuthorizeNode in the lib accepts the option WithState
|
|
// so that the workerId is passed through to storage and associated with a WorkerAuth record
|
|
func AttachWorkerIdToState(ctx context.Context, workerId string) (*structpb.Struct, error) {
|
|
const op = "server.(Worker).AttachWorkerIdToState"
|
|
if workerId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing workerId")
|
|
}
|
|
|
|
workerMap := &workerAuthWorkerId{WorkerId: workerId}
|
|
s := structs.New(workerMap)
|
|
s.TagName = "mapstructure"
|
|
return structpb.NewStruct(s.Map())
|
|
}
|
|
|
|
// A Worker is a server that provides an address which can be used to proxy
|
|
// session connections. It can be tagged with custom tags and is used when
|
|
// authorizing and establishing a session. It is owned by a scope.
|
|
type Worker struct {
|
|
*store.Worker
|
|
|
|
activeConnectionCount uint32 `gorm:"-"`
|
|
apiTags []*Tag `gorm:"-"`
|
|
configTags []*Tag `gorm:"-"`
|
|
|
|
// inputTags is not specified to be api or config tags and is not intended
|
|
// to be read by clients. Since config tags and api tags are applied in
|
|
// mutually exclusive contexts, inputTags is interpreted to be one or the
|
|
// other based on the context in which the worker is passed. As such
|
|
// inputTags should only be read when performing mutations on the database.
|
|
inputTags []*Tag `gorm:"-"`
|
|
|
|
// This is used to pass the token back to the calling function
|
|
ControllerGeneratedActivationToken string `gorm:"-"`
|
|
}
|
|
|
|
// NewWorker returns a new Worker. Valid options are WithName, WithDescription
|
|
// WithAddress, and WithWorkerTags. All other options are ignored. This does
|
|
// not set any of the worker reported values.
|
|
func NewWorker(scopeId string, opt ...Option) *Worker {
|
|
opts := GetOpts(opt...)
|
|
worker := &Worker{
|
|
Worker: &store.Worker{
|
|
ScopeId: scopeId,
|
|
Name: opts.withName,
|
|
Description: opts.withDescription,
|
|
Address: opts.withAddress,
|
|
ReleaseVersion: opts.withReleaseVersion,
|
|
OperationalState: opts.withOperationalState,
|
|
},
|
|
inputTags: opts.withWorkerTags,
|
|
}
|
|
if opts.withTestUseInputTagsAsApiTags {
|
|
worker.apiTags = worker.inputTags
|
|
}
|
|
return worker
|
|
}
|
|
|
|
// allocWorker will allocate a Worker
|
|
func allocWorker() Worker {
|
|
return Worker{Worker: &store.Worker{}}
|
|
}
|
|
|
|
func (w *Worker) clone() *Worker {
|
|
if w == nil {
|
|
return nil
|
|
}
|
|
cw := proto.Clone(w.Worker)
|
|
cWorker := &Worker{
|
|
Worker: cw.(*store.Worker),
|
|
}
|
|
if w.apiTags != nil {
|
|
cWorker.apiTags = make([]*Tag, 0, len(w.apiTags))
|
|
for _, t := range w.apiTags {
|
|
cWorker.apiTags = append(cWorker.apiTags, &Tag{Key: t.Key, Value: t.Value})
|
|
}
|
|
}
|
|
if w.configTags != nil {
|
|
cWorker.configTags = make([]*Tag, 0, len(w.configTags))
|
|
for _, t := range w.configTags {
|
|
cWorker.configTags = append(cWorker.configTags, &Tag{Key: t.Key, Value: t.Value})
|
|
}
|
|
}
|
|
if w.inputTags != nil {
|
|
cWorker.inputTags = make([]*Tag, 0, len(w.inputTags))
|
|
for _, t := range w.inputTags {
|
|
cWorker.inputTags = append(cWorker.inputTags, &Tag{Key: t.Key, Value: t.Value})
|
|
}
|
|
}
|
|
return cWorker
|
|
}
|
|
|
|
// ActiveConnectionCount is the current number of sessions this worker is handling
|
|
// according to the controllers.
|
|
func (w *Worker) ActiveConnectionCount() uint32 {
|
|
return w.activeConnectionCount
|
|
}
|
|
|
|
// CanonicalTags is the deduplicated set of tags contained on both the resource
|
|
// set over the API as well as the tags reported by the worker itself. This
|
|
// function is guaranteed to return a non-nil map.
|
|
func (w *Worker) CanonicalTags(opt ...Option) map[string][]string {
|
|
dedupedTags := make(map[Tag]struct{})
|
|
for _, t := range w.apiTags {
|
|
dedupedTags[*t] = struct{}{}
|
|
}
|
|
for _, t := range w.configTags {
|
|
dedupedTags[*t] = struct{}{}
|
|
}
|
|
tags := make(map[string][]string)
|
|
for t := range dedupedTags {
|
|
tags[t.Key] = append(tags[t.Key], t.Value)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// GetConfigTags returns the tags for this worker which has been set through
|
|
// the worker daemon's configuration file.
|
|
func (w *Worker) GetConfigTags() map[string][]string {
|
|
tags := make(map[string][]string)
|
|
for _, t := range w.configTags {
|
|
tags[t.Key] = append(tags[t.Key], t.Value)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// GetApiTags returns the api tags which have been set for this worker.
|
|
func (w *Worker) GetApiTags() map[string][]string {
|
|
tags := make(map[string][]string)
|
|
for _, t := range w.apiTags {
|
|
tags[t.Key] = append(tags[t.Key], t.Value)
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// GetLastStatusTime contains the last time the worker has reported to the
|
|
// controller its connection status. If the worker has never reported to a
|
|
// controller then nil is returned.
|
|
func (w *Worker) GetLastStatusTime() *timestamp.Timestamp {
|
|
if w == nil || w.Worker == nil || w.Worker.GetLastStatusTime().AsTime() == timestamp.NegativeInfinityTS {
|
|
return nil
|
|
}
|
|
return w.Worker.GetLastStatusTime()
|
|
}
|
|
|
|
// TableName overrides the table name used by Worker to `server_worker`
|
|
func (Worker) TableName() string {
|
|
return "server_worker"
|
|
}
|
|
|
|
// workerAggregate contains an aggregated view of the values associated with
|
|
// a single worker.
|
|
type workerAggregate struct {
|
|
PublicId string `gorm:"primary_key"`
|
|
ScopeId string
|
|
Name string
|
|
Description string
|
|
CreateTime *timestamp.Timestamp
|
|
UpdateTime *timestamp.Timestamp
|
|
Address string
|
|
Version uint32
|
|
Type string
|
|
ReleaseVersion string
|
|
ApiTags string
|
|
ActiveConnectionCount uint32
|
|
OperationalState string
|
|
// Config Fields
|
|
LastStatusTime *timestamp.Timestamp
|
|
WorkerConfigTags string
|
|
}
|
|
|
|
func (a *workerAggregate) toWorker(ctx context.Context) (*Worker, error) {
|
|
const op = "server.(workerAggregate).toWorker"
|
|
worker := &Worker{
|
|
Worker: &store.Worker{
|
|
PublicId: a.PublicId,
|
|
Name: a.Name,
|
|
Description: a.Description,
|
|
Address: a.Address,
|
|
CreateTime: a.CreateTime,
|
|
UpdateTime: a.UpdateTime,
|
|
ScopeId: a.ScopeId,
|
|
Version: a.Version,
|
|
LastStatusTime: a.LastStatusTime,
|
|
Type: a.Type,
|
|
ReleaseVersion: a.ReleaseVersion,
|
|
OperationalState: a.OperationalState,
|
|
},
|
|
activeConnectionCount: a.ActiveConnectionCount,
|
|
}
|
|
tags, err := tagsFromAggregatedTagString(ctx, a.ApiTags)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing config tag string"))
|
|
}
|
|
worker.apiTags = tags
|
|
|
|
tags, err = tagsFromAggregatedTagString(ctx, a.WorkerConfigTags)
|
|
if err != nil {
|
|
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error parsing config tag string"))
|
|
}
|
|
worker.configTags = tags
|
|
|
|
return worker, nil
|
|
}
|
|
|
|
// tagsForAggregatedTagString parses a deliminated string in the format returned
|
|
// by the database for the server_worker_aggregate view and returns []*Tag.
|
|
// The string is in the format of key1Yvalue1Zkey2Yvalue2Zkey3Yvalue3. Y and Z
|
|
// ares chosen for deliminators since tag keys and values are restricted from
|
|
// having capitalized letters in them.
|
|
func tagsFromAggregatedTagString(ctx context.Context, s string) ([]*Tag, error) {
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
const op = "server.tagsFromAggregatedTagString"
|
|
const aggregateDelimiter = "Z"
|
|
const pairDelimiter = "Y"
|
|
var tags []*Tag
|
|
for _, kv := range strings.Split(s, aggregateDelimiter) {
|
|
res := strings.SplitN(kv, pairDelimiter, 3)
|
|
if len(res) != 2 {
|
|
return nil, errors.New(ctx, errors.Internal, op, "invalid aggregated tag pairs")
|
|
}
|
|
tags = append(tags, &Tag{
|
|
Key: res[0],
|
|
Value: res[1],
|
|
})
|
|
}
|
|
return tags, nil
|
|
}
|
|
|
|
func (a *workerAggregate) GetPublicId() string {
|
|
return a.PublicId
|
|
}
|
|
|
|
func (workerAggregate) TableName() string {
|
|
return "server_worker_aggregate"
|
|
}
|