Add an installer script resource type and public HTTP endpoint (#13146)

* Add an installer script resource type and HTTP endpoint

* Add default installer scripts

* Resolve comments

- run `shfmt` over the installer script
- add MustNewInstaller
- move default installer logic to GetInstaller
- only include PublicProxyAddr in server.Config

* Resolve comments

- move from auth_with_roles -> auth/grpcserver
- document `MustNewInstallerV1`
This commit is contained in:
Alex McGrath 2022-08-15 11:27:33 +01:00 committed by GitHub
parent 321d3482dd
commit f56dd09ebd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 2321 additions and 774 deletions

View file

@ -2273,6 +2273,31 @@ func (c *Client) GetClusterAuditConfig(ctx context.Context) (types.ClusterAuditC
return resp, nil
}
// GetInstaller gets the cluster installer resource
func (c *Client) GetInstaller(ctx context.Context) (types.Installer, error) {
resp, err := c.grpc.GetInstaller(ctx, &empty.Empty{}, c.callOpts...)
if err != nil {
return nil, trail.FromGRPC(err)
}
return resp, nil
}
// GetInstaller sets the cluster installer resource
func (c *Client) SetInstaller(ctx context.Context, inst types.Installer) error {
instV1, ok := inst.(*types.InstallerV1)
if !ok {
return trace.BadParameter("invalid type %T", inst)
}
_, err := c.grpc.SetInstaller(ctx, instV1, c.callOpts...)
return trail.FromGRPC(err)
}
// GetInstaller deletes the cluster installer resource
func (c *Client) DeleteInstaller(ctx context.Context) error {
_, err := c.grpc.DeleteInstaller(ctx, &empty.Empty{}, c.callOpts...)
return trail.FromGRPC(err)
}
// GetLock gets a lock by name.
func (c *Client) GetLock(ctx context.Context, name string) (types.Lock, error) {
if name == "" {

View file

@ -162,6 +162,10 @@ func EventToGRPC(in types.Event) (*proto.Event, error) {
out.Resource = &proto.Event_WindowsDesktop{
WindowsDesktop: r,
}
case *types.InstallerV1:
out.Resource = &proto.Event_Installer{
Installer: r,
}
default:
return nil, trace.BadParameter("resource type %T is not supported", in.Resource)
}
@ -287,6 +291,9 @@ func EventFromGRPC(in proto.Event) (*types.Event, error) {
} else if r := in.GetKubernetesCluster(); r != nil {
out.Resource = r
return &out, nil
} else if r := in.GetInstaller(); r != nil {
out.Resource = r
return &out, nil
} else {
return nil, trace.BadParameter("received unsupported resource %T", in.Resource)
}

File diff suppressed because it is too large Load diff

View file

@ -130,6 +130,8 @@ message Event {
// KubernetesCluster is an Kubernetes cluster resource.
types.KubernetesClusterV3 KubernetesCluster = 33
[ (gogoproto.jsontag) = "kubernetes_cluster,omitempty" ];
// Installer is an installer resource
types.InstallerV1 Installer = 34 [ (gogoproto.jsontag) = "installer,omitempty" ];
}
}
@ -2535,6 +2537,15 @@ service AuthService {
// is used to gain access to privileged actions eg: deleting/adding a MFA device.
rpc CreatePrivilegeToken(CreatePrivilegeTokenRequest) returns (types.UserTokenV3);
// GetInstaller retrieves the installer script resource
rpc GetInstaller(google.protobuf.Empty) returns (types.InstallerV1);
// SetInstaller sets the installer script resource
rpc SetInstaller(types.InstallerV1) returns (google.protobuf.Empty);
// DeleteInstaller removes the installer script resource
rpc DeleteInstaller(google.protobuf.Empty) returns (google.protobuf.Empty);
// ListResources retrieves a paginated list of resources.
rpc ListResources(ListResourcesRequest) returns (ListResourcesResponse);

View file

@ -185,6 +185,10 @@ const (
// cluster networking configuration.
MetaNameClusterNetworkingConfig = "cluster-networking-config"
// MetaNameClusterInstallerScript is the exact name of the
// singleton resource holding an installer script
MetaNameClusterInstallerScript = "cluster-install-script"
// KindSemaphore is the resource that provides distributed semaphore functionality
KindSemaphore = "semaphore"
@ -266,6 +270,10 @@ const (
// KindDatabaseCertificate is a resource to control Database Certificates generation
KindDatabaseCertificate = "database_certificate"
// KindInstaller is a resource that holds a node installer script
// used to install teleport on discovered nodes
KindInstaller = "installer"
// V5 is the fifth version of resources.
V5 = "v5"

136
api/types/installer.go Normal file
View file

@ -0,0 +1,136 @@
/**
* Copyright 2022 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package types
import (
"time"
"github.com/gravitational/trace"
)
// Installer is an installer script rseource
type Installer interface {
Resource
// GetScript returns the contents of the installer script
GetScript() string
// SetScript sets the installer script
SetScript(string)
String() string
}
// NewInstallerV1 returns a new installer resource
func NewInstallerV1(script string) (*InstallerV1, error) {
installer := &InstallerV1{
Metadata: Metadata{},
Spec: InstallerSpecV1{
Script: script,
},
}
if err := installer.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return installer, nil
}
// MustNewInstallerV1 creates a new installer resource from the provided script.
//
// Panics in case of any error when creating the resource.
func MustNewInstallerV1(script string) *InstallerV1 {
inst, err := NewInstallerV1(script)
if err != nil {
panic(err)
}
return inst
}
// CheckAndSetDefaults implements Installer
func (c *InstallerV1) CheckAndSetDefaults() error {
c.setStaticFields()
return trace.Wrap(c.Metadata.CheckAndSetDefaults())
}
// GetVersion returns resource version.
func (c *InstallerV1) GetVersion() string {
return c.Version
}
// GetName returns the name of the resource.
func (c *InstallerV1) GetName() string {
return c.Metadata.Name
}
// SetName sets the name of the resource.
func (c *InstallerV1) SetName(e string) {
c.Metadata.Name = e
}
// SetExpiry sets expiry time for the object.
func (c *InstallerV1) SetExpiry(expires time.Time) {
c.Metadata.SetExpiry(expires)
}
// Expiry returns object expiry setting.
func (c *InstallerV1) Expiry() time.Time {
return c.Metadata.Expiry()
}
// GetMetadata returns object metadata.
func (c *InstallerV1) GetMetadata() Metadata {
return c.Metadata
}
// GetResourceID returns resource ID.
func (c *InstallerV1) GetResourceID() int64 {
return c.Metadata.ID
}
// SetResourceID sets resource ID.
func (c *InstallerV1) SetResourceID(id int64) {
c.Metadata.ID = id
}
// GetKind returns resource kind.
func (c *InstallerV1) GetKind() string {
return c.Kind
}
// GetSubKind returns resource subkind.
func (c *InstallerV1) GetSubKind() string {
return c.SubKind
}
// SetSubKind sets resource subkind.
func (c *InstallerV1) SetSubKind(sk string) {
c.SubKind = sk
}
func (c *InstallerV1) GetScript() string {
return c.Spec.Script
}
func (c *InstallerV1) SetScript(s string) {
c.Spec.Script = s
}
// setStaticFields sets static resource header and metadata fields.
func (c *InstallerV1) setStaticFields() {
c.Kind = KindInstaller
c.Version = V1
c.Metadata.Name = MetaNameClusterInstallerScript
}

View file

@ -0,0 +1,45 @@
#!/bin/sh
(
flock -n 9 || exit 1
if test -f /usr/local/bin/teleport; then
exit 0
fi
distro_id="$(awk -F= '$1 == "ID" { print tolower($2) }' /etc/os-release | xargs echo)"
if [ "$distro_id" = "debian" ] || [ "$distro_id" = "ubuntu" ]; then
sudo curl https://deb.releases.teleport.dev/teleport-pubkey.asc \
-o /usr/share/keyrings/teleport-archive-keyring.asc
. /etc/os-release
echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" |
sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null
sudo apt-get update
sudo apt-get install teleport jq
elif [ "$distro_id" = "amzn" ] || [ "$distro_id" = "rhel" ]; then
. /etc/os-release
sudo yum-config-manager --add-repo \
"$(rpm --eval "https://yum.releases.teleport.dev/$ID/$VERSION_ID/Teleport/%{_arch}/stable/{{ .MajorVersion }}/teleport.repo")"
sudo yum install -y teleport jq
else
echo "Unsupported distro: $distro_id"
exit 1
fi
INSTANCE_INFO="$(curl http://169.254.169.254/latest/dynamic/instance-identity/document)"
ACCOUNT_ID="$(echo "$INSTANCE_INFO" | jq -r .accountId)"
INSTANCE_ID="$(echo "$INSTANCE_INFO" | jq -r .instanceId)"
# generate teleport ssh config
# token is read as a parameter from the AWS ssm script run and
# passed as the first argument to the script
sudo /usr/local/bin/teleport node configure \
--auth-server="{{ .PublicProxyAddr }}" \
--join-method=iam \
--token="$1" \
--output=file \
--labels="teleport.dev/instance-id=${INSTANCE_ID},teleport.dev/account-id=${ACCOUNT_ID}"
# enable and start teleport service
sudo systemctl enable --now teleport
) 9>/var/lock/teleport_install.lock

View file

@ -0,0 +1,39 @@
/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package installers
import (
_ "embed"
"github.com/gravitational/teleport/api/types"
)
//go:embed installer.sh.tmpl
var defaultInstallScript string
// DefaultInstaller represents a the default installer script provided
// by teleport
var DefaultInstaller = types.MustNewInstallerV1(defaultInstallScript)
// Template is used to fill proxy address and version information into
// the installer script
type Template struct {
// PublicProxyAddr is public address of the proxy
PublicProxyAddr string
// MajorVersion is the major version of the Teleport auth node
MajorVersion string
}

View file

@ -10022,6 +10022,99 @@ func (m *Participant) XXX_DiscardUnknown() {
var xxx_messageInfo_Participant proto.InternalMessageInfo
// InstallerV1 represents an installer script resource. Used to
// provide a script to install teleport on discovered nodes.
type InstallerV1 struct {
// Kind is the resource kind.
Kind string `protobuf:"bytes,1,opt,name=Kind,proto3" json:"kind"`
// SubKind is an optional resource subkind. Currently unused for this resource.
SubKind string `protobuf:"bytes,2,opt,name=SubKind,proto3" json:"sub_kind,omitempty"`
// Version is the resource version.
Version string `protobuf:"bytes,3,opt,name=Version,proto3" json:"version"`
// Metadata is the resource metadata.
Metadata Metadata `protobuf:"bytes,4,opt,name=Metadata,proto3" json:"metadata"`
// Spec is the resource spec.
Spec InstallerSpecV1 `protobuf:"bytes,5,opt,name=Spec,proto3" json:"spec"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *InstallerV1) Reset() { *m = InstallerV1{} }
func (m *InstallerV1) String() string { return proto.CompactTextString(m) }
func (*InstallerV1) ProtoMessage() {}
func (*InstallerV1) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{188}
}
func (m *InstallerV1) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *InstallerV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_InstallerV1.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *InstallerV1) XXX_Merge(src proto.Message) {
xxx_messageInfo_InstallerV1.Merge(m, src)
}
func (m *InstallerV1) XXX_Size() int {
return m.Size()
}
func (m *InstallerV1) XXX_DiscardUnknown() {
xxx_messageInfo_InstallerV1.DiscardUnknown(m)
}
var xxx_messageInfo_InstallerV1 proto.InternalMessageInfo
// InstallerSpecV1 is the specification for an Installer
type InstallerSpecV1 struct {
// Script represents the contents of a installer shell script
Script string `protobuf:"bytes,1,opt,name=Script,proto3" json:"script"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *InstallerSpecV1) Reset() { *m = InstallerSpecV1{} }
func (m *InstallerSpecV1) String() string { return proto.CompactTextString(m) }
func (*InstallerSpecV1) ProtoMessage() {}
func (*InstallerSpecV1) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{189}
}
func (m *InstallerSpecV1) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *InstallerSpecV1) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_InstallerSpecV1.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *InstallerSpecV1) XXX_Merge(src proto.Message) {
xxx_messageInfo_InstallerSpecV1.Merge(m, src)
}
func (m *InstallerSpecV1) XXX_Size() int {
return m.Size()
}
func (m *InstallerSpecV1) XXX_DiscardUnknown() {
xxx_messageInfo_InstallerSpecV1.DiscardUnknown(m)
}
var xxx_messageInfo_InstallerSpecV1 proto.InternalMessageInfo
// SortBy defines a sort criteria.
type SortBy struct {
// IsDesc is a sort direction flag where if true the direction is descending, else ascending.
@ -10037,7 +10130,7 @@ func (m *SortBy) Reset() { *m = SortBy{} }
func (m *SortBy) String() string { return proto.CompactTextString(m) }
func (*SortBy) ProtoMessage() {}
func (*SortBy) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{188}
return fileDescriptor_d938547f84707355, []int{190}
}
func (m *SortBy) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -10084,7 +10177,7 @@ func (m *ConnectionDiagnosticV1) Reset() { *m = ConnectionDiagnosticV1{}
func (m *ConnectionDiagnosticV1) String() string { return proto.CompactTextString(m) }
func (*ConnectionDiagnosticV1) ProtoMessage() {}
func (*ConnectionDiagnosticV1) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{189}
return fileDescriptor_d938547f84707355, []int{191}
}
func (m *ConnectionDiagnosticV1) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -10125,7 +10218,7 @@ func (m *ConnectionDiagnosticSpecV1) Reset() { *m = ConnectionDiagnostic
func (m *ConnectionDiagnosticSpecV1) String() string { return proto.CompactTextString(m) }
func (*ConnectionDiagnosticSpecV1) ProtoMessage() {}
func (*ConnectionDiagnosticSpecV1) Descriptor() ([]byte, []int) {
return fileDescriptor_d938547f84707355, []int{190}
return fileDescriptor_d938547f84707355, []int{192}
}
func (m *ConnectionDiagnosticSpecV1) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -10370,6 +10463,8 @@ func init() {
proto.RegisterType((*SessionTrackerSpecV1)(nil), "types.SessionTrackerSpecV1")
proto.RegisterType((*SessionTrackerPolicySet)(nil), "types.SessionTrackerPolicySet")
proto.RegisterType((*Participant)(nil), "types.Participant")
proto.RegisterType((*InstallerV1)(nil), "types.InstallerV1")
proto.RegisterType((*InstallerSpecV1)(nil), "types.InstallerSpecV1")
proto.RegisterType((*SortBy)(nil), "types.SortBy")
proto.RegisterType((*ConnectionDiagnosticV1)(nil), "types.ConnectionDiagnosticV1")
proto.RegisterType((*ConnectionDiagnosticSpecV1)(nil), "types.ConnectionDiagnosticSpecV1")
@ -10378,7 +10473,7 @@ func init() {
func init() { proto.RegisterFile("types.proto", fileDescriptor_d938547f84707355) }
var fileDescriptor_d938547f84707355 = []byte{
// 14224 bytes of a gzipped FileDescriptorProto
// 14263 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0xbd, 0x7d, 0x6c, 0x1c, 0x49,
0x76, 0x18, 0xae, 0x9e, 0x19, 0x92, 0xc3, 0xc7, 0x21, 0x39, 0x2c, 0x52, 0x12, 0xa5, 0xd5, 0x2e,
0xb5, 0xbd, 0xbb, 0x5a, 0xad, 0x76, 0x57, 0x3a, 0x51, 0xb7, 0x3a, 0xef, 0xed, 0xd7, 0xcd, 0x90,
@ -11237,37 +11332,40 @@ var fileDescriptor_d938547f84707355 = []byte{
0x8d, 0x2f, 0xf3, 0xf4, 0x4b, 0x0a, 0x87, 0x1c, 0x57, 0xe7, 0x28, 0x99, 0xb9, 0xe3, 0x5d, 0x20,
0xf8, 0x98, 0x60, 0x3e, 0xc1, 0x6b, 0xa6, 0xfc, 0x15, 0xf8, 0x78, 0xe0, 0x17, 0x01, 0x56, 0x9c,
0x30, 0xaa, 0xd4, 0x23, 0xef, 0x3e, 0x1d, 0x40, 0x73, 0x27, 0x09, 0xcc, 0x1d, 0x7c, 0x04, 0x96,
0x91, 0x75, 0x25, 0x30, 0x8f, 0x19, 0x9a, 0xab, 0x30, 0x5c, 0xf3, 0x83, 0xa8, 0x7a, 0xc0, 0x97,
0xe3, 0x45, 0x1a, 0xd6, 0x55, 0x4f, 0xa6, 0x87, 0x3e, 0x8d, 0xba, 0x25, 0x8a, 0x98, 0x4d, 0x7c,
0xcb, 0xa3, 0x0d, 0x57, 0x8d, 0x30, 0xd9, 0x61, 0x00, 0x8b, 0xc3, 0x99, 0xc9, 0x72, 0x26, 0xc9,
0x70, 0x94, 0x84, 0xb2, 0x3c, 0xea, 0xaa, 0xbd, 0xa0, 0x19, 0x52, 0xcf, 0xea, 0x19, 0xc6, 0xb5,
0x9a, 0xfa, 0xac, 0xdd, 0x1f, 0xc0, 0xf9, 0xde, 0x14, 0x6a, 0x70, 0x8c, 0xd1, 0x27, 0x38, 0xe6,
0x85, 0xb4, 0xe3, 0x0d, 0xd1, 0x84, 0xe3, 0x2d, 0x76, 0xb7, 0x5d, 0x79, 0x07, 0x26, 0xe5, 0x0c,
0xdf, 0x58, 0xa9, 0xe1, 0xa7, 0x9c, 0x84, 0xb1, 0x7b, 0x4b, 0xd6, 0xf2, 0xad, 0xcf, 0xd9, 0xb7,
0x36, 0x57, 0x56, 0xca, 0xa7, 0xc8, 0x38, 0x8c, 0x0a, 0xc0, 0x42, 0xa5, 0x6c, 0x90, 0x12, 0x14,
0x97, 0x57, 0x6b, 0x4b, 0x0b, 0x9b, 0xd6, 0x52, 0x39, 0x77, 0xe5, 0x05, 0x98, 0x48, 0x82, 0xae,
0xf1, 0x84, 0x6b, 0x04, 0xf2, 0x56, 0x65, 0xab, 0x7c, 0x8a, 0x00, 0x0c, 0xaf, 0xdf, 0x59, 0xa8,
0x5d, 0xbf, 0x5e, 0x36, 0xae, 0x7c, 0x02, 0xa6, 0x70, 0xd7, 0xba, 0xc2, 0x36, 0x50, 0x2d, 0x1a,
0x60, 0x4d, 0x25, 0x28, 0xd6, 0x68, 0xdb, 0x09, 0x9c, 0x88, 0xf2, 0x6a, 0xee, 0x76, 0x1a, 0x91,
0xd7, 0x6e, 0xd0, 0x87, 0x65, 0xe3, 0xca, 0xeb, 0x30, 0x69, 0xf9, 0x9d, 0xc8, 0x6b, 0xed, 0xca,
0x77, 0x80, 0xc9, 0x69, 0x98, 0xda, 0x5c, 0xad, 0xdc, 0xad, 0x2e, 0xbf, 0xbb, 0xb9, 0xb6, 0x59,
0xb3, 0xef, 0x56, 0x36, 0x16, 0x6e, 0x97, 0x4f, 0xb1, 0x06, 0xdf, 0x5d, 0xab, 0x6d, 0xd8, 0xd6,
0xd2, 0xc2, 0xd2, 0xea, 0x46, 0xd9, 0xb8, 0xf2, 0x33, 0x06, 0x4c, 0x30, 0xe9, 0xc5, 0xfd, 0xcf,
0x26, 0xba, 0x15, 0x2f, 0xc2, 0x85, 0xcd, 0xda, 0x92, 0x65, 0x6f, 0xac, 0xdd, 0x59, 0x5a, 0xb5,
0x37, 0x6b, 0x95, 0x77, 0x97, 0xec, 0xcd, 0xd5, 0xda, 0xfa, 0xd2, 0xc2, 0xf2, 0xad, 0xe5, 0xa5,
0xc5, 0xf2, 0x29, 0x32, 0x07, 0x4f, 0x29, 0x18, 0xd6, 0xd2, 0xc2, 0xda, 0xbd, 0x25, 0xcb, 0x5e,
0xaf, 0xd4, 0x6a, 0x5b, 0x6b, 0xd6, 0x62, 0xd9, 0x20, 0xe7, 0xe1, 0x4c, 0x06, 0xc2, 0xdd, 0x5b,
0x95, 0x72, 0xae, 0xab, 0x6c, 0x75, 0x69, 0xab, 0xb2, 0x62, 0x57, 0xd7, 0x36, 0xca, 0xf9, 0x2b,
0xef, 0x30, 0x0b, 0x54, 0x3c, 0x68, 0xcc, 0x2c, 0x9c, 0x22, 0x14, 0x56, 0xd7, 0x56, 0x97, 0xca,
0xa7, 0xc8, 0x18, 0x8c, 0xac, 0x2f, 0xad, 0x2e, 0x2e, 0xaf, 0xbe, 0xcb, 0x87, 0xb5, 0xb2, 0xbe,
0x6e, 0xad, 0xdd, 0x5b, 0x5a, 0x2c, 0xe7, 0xd8, 0xd8, 0x2d, 0x2e, 0xad, 0xb2, 0x96, 0xe5, 0xaf,
0x98, 0xfc, 0x9d, 0x6d, 0xed, 0xf5, 0x4e, 0x36, 0x5a, 0x4b, 0x9f, 0xdd, 0x58, 0x5a, 0xad, 0x2d,
0xaf, 0xad, 0x96, 0x4f, 0x5d, 0xb9, 0x90, 0xc2, 0x91, 0x5f, 0xa2, 0x56, 0xbb, 0x5d, 0x3e, 0x75,
0xe5, 0x0b, 0x50, 0x52, 0x0d, 0x30, 0x72, 0x16, 0xa6, 0xd5, 0xdf, 0xeb, 0xb4, 0xe5, 0x7a, 0xad,
0xdd, 0xf2, 0xa9, 0x74, 0x81, 0xd5, 0x69, 0xb5, 0x58, 0x01, 0x76, 0x5e, 0x2d, 0xd8, 0xa0, 0x41,
0xd3, 0x6b, 0x31, 0xdb, 0xaa, 0x9c, 0xab, 0x96, 0xbf, 0xfb, 0xc7, 0xcf, 0x9c, 0xfa, 0xee, 0xf7,
0x9f, 0x31, 0xfe, 0xe8, 0xfb, 0xcf, 0x18, 0xff, 0xed, 0xfb, 0xcf, 0x18, 0xdb, 0xc3, 0x38, 0xe3,
0x6f, 0xfc, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xba, 0xb9, 0x2b, 0x33, 0x1c, 0xe2, 0x00, 0x00,
0x91, 0x75, 0x25, 0x30, 0x8f, 0x19, 0x9a, 0x7f, 0x69, 0xc0, 0xd8, 0x72, 0x2b, 0x8c, 0x9c, 0x46,
0x03, 0x97, 0xd6, 0x8f, 0x53, 0xaa, 0xba, 0xb8, 0x5f, 0x7d, 0x96, 0xf3, 0xd7, 0x60, 0x32, 0x85,
0xc6, 0xb6, 0x1c, 0x35, 0xbc, 0x8e, 0xa2, 0x6e, 0x39, 0xf8, 0x05, 0x15, 0x4b, 0x94, 0x98, 0xab,
0x30, 0x5c, 0xf3, 0x83, 0xa8, 0x7a, 0xc0, 0xad, 0x98, 0x45, 0x1a, 0xd6, 0x55, 0x07, 0xb0, 0x87,
0xae, 0xa0, 0xba, 0x25, 0x8a, 0xd8, 0x56, 0xe2, 0x96, 0x47, 0x1b, 0xae, 0x1a, 0x98, 0xb3, 0xc3,
0x00, 0x16, 0x87, 0x33, 0x4b, 0xef, 0x4c, 0x92, 0x18, 0x2a, 0x89, 0x00, 0x7a, 0x54, 0x63, 0x67,
0x41, 0x1b, 0x98, 0x67, 0xf5, 0xc4, 0xec, 0x5a, 0x4d, 0x7d, 0xc6, 0xe8, 0x03, 0x38, 0xdf, 0x9b,
0x42, 0x8d, 0x29, 0x32, 0xfa, 0xc4, 0x14, 0xbd, 0x90, 0xf6, 0x57, 0x22, 0x9a, 0xf0, 0x57, 0xc6,
0x5e, 0xca, 0x2b, 0xef, 0xc0, 0xa4, 0x54, 0x8c, 0x1b, 0x2b, 0x35, 0x9c, 0x01, 0x93, 0x30, 0x76,
0x6f, 0xc9, 0x5a, 0xbe, 0xf5, 0x39, 0xfb, 0xd6, 0xe6, 0xca, 0x4a, 0xf9, 0x14, 0x19, 0x87, 0x51,
0x01, 0x58, 0xa8, 0x94, 0x0d, 0x52, 0x82, 0xe2, 0xf2, 0x6a, 0x6d, 0x69, 0x61, 0xd3, 0x5a, 0x2a,
0xe7, 0xae, 0xbc, 0x00, 0x13, 0x49, 0xac, 0x3a, 0x1e, 0x0c, 0x8e, 0x40, 0xde, 0xaa, 0x6c, 0x95,
0x4f, 0x11, 0x80, 0xe1, 0xf5, 0x3b, 0x0b, 0xb5, 0xeb, 0xd7, 0xcb, 0xc6, 0x95, 0x4f, 0xc0, 0x14,
0x6e, 0xf6, 0x57, 0xd8, 0xbe, 0xb3, 0x45, 0x03, 0xac, 0xa9, 0x04, 0xc5, 0x1a, 0x6d, 0x3b, 0x81,
0x13, 0x51, 0x5e, 0xcd, 0xdd, 0x4e, 0x23, 0xf2, 0xda, 0x0d, 0xfa, 0xb0, 0x6c, 0x5c, 0x79, 0x1d,
0x26, 0x2d, 0xbf, 0x13, 0x79, 0xad, 0x5d, 0xf9, 0x7c, 0x32, 0x39, 0x0d, 0x53, 0x9b, 0xab, 0x95,
0xbb, 0xd5, 0xe5, 0x77, 0x37, 0xd7, 0x36, 0x6b, 0xf6, 0xdd, 0xca, 0xc6, 0xc2, 0xed, 0xf2, 0x29,
0xd6, 0xe0, 0xbb, 0x6b, 0xb5, 0x0d, 0xdb, 0x5a, 0x5a, 0x58, 0x5a, 0xdd, 0x28, 0x1b, 0x57, 0x7e,
0xc6, 0x80, 0x09, 0x36, 0xe9, 0x71, 0xdb, 0xb8, 0x89, 0xde, 0xd8, 0x8b, 0x70, 0x61, 0xb3, 0xb6,
0x64, 0xd9, 0x1b, 0x6b, 0x77, 0x96, 0x56, 0xed, 0xcd, 0x5a, 0xe5, 0xdd, 0x25, 0x7b, 0x73, 0xb5,
0xb6, 0xbe, 0xb4, 0xb0, 0x7c, 0x6b, 0x79, 0x69, 0xb1, 0x7c, 0x8a, 0xcc, 0xc1, 0x53, 0x0a, 0x86,
0xb5, 0xb4, 0xb0, 0x76, 0x6f, 0xc9, 0xb2, 0xd7, 0x2b, 0xb5, 0xda, 0xd6, 0x9a, 0xb5, 0x58, 0x36,
0xc8, 0x79, 0x38, 0x93, 0x81, 0x70, 0xf7, 0x56, 0xa5, 0x9c, 0xeb, 0x2a, 0x5b, 0x5d, 0xda, 0xaa,
0xac, 0xd8, 0xd5, 0xb5, 0x8d, 0x72, 0xfe, 0xca, 0x3b, 0xcc, 0x70, 0x17, 0xef, 0x40, 0x33, 0xc3,
0xb0, 0x08, 0x85, 0xd5, 0xb5, 0xd5, 0xa5, 0xf2, 0x29, 0x32, 0x06, 0x23, 0xeb, 0x4b, 0xab, 0x8b,
0xcb, 0xab, 0xef, 0xf2, 0x61, 0xad, 0xac, 0xaf, 0x5b, 0x6b, 0xf7, 0x96, 0x16, 0xcb, 0x39, 0x36,
0x76, 0x8b, 0x4b, 0xab, 0xac, 0x65, 0xf9, 0x2b, 0x26, 0x7f, 0x9e, 0x5c, 0x7b, 0xf4, 0x94, 0x8d,
0xd6, 0xd2, 0x67, 0x37, 0x96, 0x56, 0x6b, 0xcb, 0x6b, 0xab, 0xe5, 0x53, 0x57, 0x2e, 0xa4, 0x70,
0xe4, 0x97, 0xa8, 0xd5, 0x6e, 0x97, 0x4f, 0x5d, 0xf9, 0x02, 0x94, 0x54, 0xbb, 0x95, 0x9c, 0x85,
0x69, 0xf5, 0xf7, 0x3a, 0x6d, 0xb9, 0x5e, 0x6b, 0xb7, 0x7c, 0x2a, 0x5d, 0x60, 0x75, 0x5a, 0x2d,
0x56, 0x80, 0x9d, 0x57, 0x0b, 0x36, 0x68, 0xd0, 0xf4, 0x5a, 0xcc, 0x24, 0x2d, 0xe7, 0xaa, 0xe5,
0xef, 0xfe, 0xf1, 0x33, 0xa7, 0xbe, 0xfb, 0xfd, 0x67, 0x8c, 0x3f, 0xfa, 0xfe, 0x33, 0xc6, 0x7f,
0xfb, 0xfe, 0x33, 0xc6, 0xf6, 0x30, 0x2a, 0xca, 0x1b, 0xff, 0x27, 0x00, 0x00, 0xff, 0xff, 0x52,
0xdd, 0xc5, 0xcf, 0x53, 0xe3, 0x00, 0x00,
}
func (m *KeepAlive) Marshal() (dAtA []byte, err error) {
@ -23851,6 +23949,108 @@ func (m *Participant) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *InstallerV1) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *InstallerV1) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *InstallerV1) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
{
size, err := m.Spec.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTypes(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x2a
{
size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTypes(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x22
if len(m.Version) > 0 {
i -= len(m.Version)
copy(dAtA[i:], m.Version)
i = encodeVarintTypes(dAtA, i, uint64(len(m.Version)))
i--
dAtA[i] = 0x1a
}
if len(m.SubKind) > 0 {
i -= len(m.SubKind)
copy(dAtA[i:], m.SubKind)
i = encodeVarintTypes(dAtA, i, uint64(len(m.SubKind)))
i--
dAtA[i] = 0x12
}
if len(m.Kind) > 0 {
i -= len(m.Kind)
copy(dAtA[i:], m.Kind)
i = encodeVarintTypes(dAtA, i, uint64(len(m.Kind)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *InstallerSpecV1) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *InstallerSpecV1) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *InstallerSpecV1) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.Script) > 0 {
i -= len(m.Script)
copy(dAtA[i:], m.Script)
i = encodeVarintTypes(dAtA, i, uint64(len(m.Script)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *SortBy) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@ -29637,6 +29837,50 @@ func (m *Participant) Size() (n int) {
return n
}
func (m *InstallerV1) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Kind)
if l > 0 {
n += 1 + l + sovTypes(uint64(l))
}
l = len(m.SubKind)
if l > 0 {
n += 1 + l + sovTypes(uint64(l))
}
l = len(m.Version)
if l > 0 {
n += 1 + l + sovTypes(uint64(l))
}
l = m.Metadata.Size()
n += 1 + l + sovTypes(uint64(l))
l = m.Spec.Size()
n += 1 + l + sovTypes(uint64(l))
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *InstallerSpecV1) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Script)
if l > 0 {
n += 1 + l + sovTypes(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *SortBy) Size() (n int) {
if m == nil {
return 0
@ -67582,6 +67826,302 @@ func (m *Participant) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *InstallerV1) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: InstallerV1: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: InstallerV1: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Kind", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Kind = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SubKind", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.SubKind = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Version = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipTypes(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTypes
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *InstallerSpecV1) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: InstallerSpecV1: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: InstallerSpecV1: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Script", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Script = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipTypes(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTypes
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *SortBy) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0

View file

@ -3587,6 +3587,27 @@ message Participant {
];
}
// InstallerV1 represents an installer script resource. Used to
// provide a script to install teleport on discovered nodes.
message InstallerV1 {
// Kind is the resource kind.
string Kind = 1 [ (gogoproto.jsontag) = "kind" ];
// SubKind is an optional resource subkind. Currently unused for this resource.
string SubKind = 2 [ (gogoproto.jsontag) = "sub_kind,omitempty" ];
// Version is the resource version.
string Version = 3 [ (gogoproto.jsontag) = "version" ];
// Metadata is the resource metadata.
Metadata Metadata = 4 [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "metadata" ];
// Spec is the resource spec.
InstallerSpecV1 Spec = 5 [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "spec" ];
}
// InstallerSpecV1 is the specification for an Installer
message InstallerSpecV1 {
// Script represents the contents of a installer shell script
string Script = 1 [ (gogoproto.jsontag) = "script" ];
}
// SessionState represents the state of a session.
enum SessionState {
// Pending variant represents a session that is waiting on participants to fulfill the criteria

View file

@ -839,6 +839,9 @@ type Cache interface {
ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error)
// ListWindowsDesktops returns a paginated list of windows desktops.
ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error)
// GetInstaller gets installer resource for this cluster
GetInstaller(ctx context.Context) (types.Installer, error)
}
type NodeWrapper struct {

View file

@ -2104,3 +2104,29 @@ func TestEnforcerGetLicenseCheckResult(t *testing.T) {
require.NoError(t, err)
require.Equal(t, expected, heartbeat.Spec.Notifications)
}
func TestInstallerCRUD(t *testing.T) {
t.Parallel()
s := newAuthSuite(t)
ctx := context.Background()
var inst types.Installer
var err error
contents := "#! just some script contents"
inst, err = types.NewInstallerV1(contents)
require.NoError(t, err)
require.NoError(t, s.a.SetInstaller(ctx, inst))
inst, err = s.a.GetInstaller(ctx)
require.NoError(t, err)
require.Equal(t, contents, inst.GetScript())
// resets to the default installer
err = s.a.DeleteInstaller(ctx)
require.NoError(t, err)
_, err = s.a.GetInstaller(ctx)
require.Error(t, err)
require.True(t, trace.IsNotFound(err))
}

View file

@ -3291,6 +3291,30 @@ func (a *ServerWithRoles) GetAuthPreference(ctx context.Context) (types.AuthPref
return a.authServer.GetAuthPreference(ctx)
}
// GetInstaller retrieves an installer script resource
func (a *ServerWithRoles) GetInstaller(ctx context.Context) (types.Installer, error) {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetInstaller(ctx)
}
// SetInstaller sets an Installer script resource
func (a *ServerWithRoles) SetInstaller(ctx context.Context, inst types.Installer) error {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbUpdate, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.SetInstaller(ctx, inst))
}
// DeleteInstaller removes an installer script resource
func (a *ServerWithRoles) DeleteInstaller(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteInstaller(ctx))
}
// SetAuthPreference sets cluster auth preference.
func (a *ServerWithRoles) SetAuthPreference(ctx context.Context, newAuthPref types.AuthPreference) error {
storedAuthPref, err := a.authServer.GetAuthPreference(ctx)

View file

@ -324,6 +324,98 @@ func TestSAMLAuthRequest(t *testing.T) {
}
}
func TestInstaller(t *testing.T) {
ctx := context.Background()
srv := newTestTLSServer(t)
_, err := CreateRole(ctx, srv.Auth(), "test-empty", types.RoleSpecV5{})
require.NoError(t, err)
_, err = CreateRole(ctx, srv.Auth(), "test-read", types.RoleSpecV5{
Allow: types.RoleConditions{
Rules: []types.Rule{
{
Resources: []string{types.KindInstaller},
Verbs: []string{types.VerbRead},
},
},
},
})
require.NoError(t, err)
_, err = CreateRole(ctx, srv.Auth(), "test-update", types.RoleSpecV5{
Allow: types.RoleConditions{
Rules: []types.Rule{
{
Resources: []string{types.KindInstaller},
Verbs: []string{types.VerbUpdate, types.VerbCreate},
},
},
},
})
require.NoError(t, err)
_, err = CreateRole(ctx, srv.Auth(), "test-delete", types.RoleSpecV5{
Allow: types.RoleConditions{
Rules: []types.Rule{
{
Resources: []string{types.KindInstaller},
Verbs: []string{types.VerbDelete},
},
},
},
})
require.NoError(t, err)
user, err := CreateUser(srv.Auth(), "testuser")
require.NoError(t, err)
inst, err := types.NewInstallerV1("contents")
require.NoError(t, err)
err = srv.Auth().SetInstaller(ctx, inst)
require.NoError(t, err)
for _, tc := range []struct {
roles []string
assert require.ErrorAssertionFunc
installerAction func(*Client) error
}{{
roles: []string{"test-empty"},
assert: require.Error,
installerAction: func(c *Client) error {
_, err := c.GetInstaller(ctx)
return err
},
}, {
roles: []string{"test-read"},
assert: require.NoError,
installerAction: func(c *Client) error {
_, err := c.GetInstaller(ctx)
return err
},
}, {
roles: []string{"test-update"},
assert: require.NoError,
installerAction: func(c *Client) error {
inst, err := types.NewInstallerV1("new-contents")
require.NoError(t, err)
return c.SetInstaller(ctx, inst)
},
}, {
roles: []string{"test-delete"},
assert: require.NoError,
installerAction: func(c *Client) error {
err := c.DeleteInstaller(ctx)
return err
},
}} {
user.SetRoles(tc.roles)
err = srv.Auth().UpsertUser(user)
require.NoError(t, err)
client, err := srv.NewClient(TestUser(user.GetName()))
require.NoError(t, err)
tc.assert(t, tc.installerAction(client))
}
}
func TestOIDCAuthRequest(t *testing.T) {
ctx := context.Background()
srv := newTestTLSServer(t)

View file

@ -46,6 +46,7 @@ import (
"github.com/gravitational/teleport/api/metadata"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/installers"
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/httplib"
@ -4198,7 +4199,50 @@ func (g *GRPCServer) CreateConnectionDiagnostic(ctx context.Context, connectionD
if err := auth.ServerWithRoles.CreateConnectionDiagnostic(ctx, connectionDiagnostic); err != nil {
return nil, trace.Wrap(err)
}
return &empty.Empty{}, nil
}
// SetInstaller sets the installer script resource
func (g *GRPCServer) SetInstaller(ctx context.Context, req *types.InstallerV1) (*empty.Empty, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := auth.SetInstaller(ctx, req); err != nil {
return nil, trace.Wrap(err)
}
return &empty.Empty{}, nil
}
// GetInstaller retrieves the installer script resource
func (g *GRPCServer) GetInstaller(ctx context.Context, _ *empty.Empty) (*types.InstallerV1, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
res, err := auth.GetInstaller(ctx)
if err != nil {
if trace.IsNotFound(err) {
return installers.DefaultInstaller, nil
}
return nil, trace.Wrap(err)
}
inst, ok := res.(*types.InstallerV1)
if !ok {
return nil, trace.BadParameter("unexpected installer type %T", res)
}
return inst, nil
}
// DeleteInstaller sets the installer script resource to its default
func (g *GRPCServer) DeleteInstaller(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := auth.DeleteInstaller(ctx); err != nil {
return nil, trace.Wrap(err)
}
return &empty.Empty{}, nil
}

View file

@ -327,6 +327,7 @@ func (a *authorizer) authorizeRemoteBuiltinRole(r RemoteBuiltinRole) (*Context,
types.NewRule(types.KindClusterAuthPreference, services.RO()),
types.NewRule(types.KindKubeService, services.RO()),
types.NewRule(types.KindKubeServer, services.RO()),
types.NewRule(types.KindInstaller, services.RO()),
// this rule allows remote proxy to update the cluster's certificate authorities
// during certificates renewal
{
@ -416,6 +417,7 @@ func roleSpecForProxy(clusterName string) types.RoleSpecV5 {
types.NewRule(types.KindWindowsDesktopService, services.RO()),
types.NewRule(types.KindDatabaseCertificate, []string{types.VerbCreate}),
types.NewRule(types.KindWindowsDesktop, services.RO()),
types.NewRule(types.KindInstaller, services.RO()),
// this rule allows local proxy to update the remote cluster's host certificate authorities
// during certificates renewal
{

18
lib/cache/cache.go vendored
View file

@ -104,6 +104,7 @@ func ForAuth(cfg Config) Config {
{Kind: types.KindWindowsDesktopService},
{Kind: types.KindWindowsDesktop},
{Kind: types.KindKubeServer},
{Kind: types.KindInstaller},
}
cfg.QueueSize = defaults.AuthQueueSize
return cfg
@ -141,6 +142,7 @@ func ForProxy(cfg Config) Config {
{Kind: types.KindWindowsDesktopService},
{Kind: types.KindWindowsDesktop},
{Kind: types.KindKubeServer},
{Kind: types.KindInstaller},
}
cfg.QueueSize = defaults.ProxyQueueSize
return cfg
@ -172,6 +174,7 @@ func ForRemoteProxy(cfg Config) Config {
{Kind: types.KindDatabaseServer},
{Kind: types.KindDatabase},
{Kind: types.KindKubeServer},
{Kind: types.KindInstaller},
}
cfg.QueueSize = defaults.ProxyQueueSize
return cfg
@ -1497,6 +1500,21 @@ func (c *Cache) GetClusterName(opts ...services.MarshalOption) (types.ClusterNam
return rg.clusterConfig.GetClusterName(opts...)
}
// GetInstaller gets the installer script resource for the cluster
func (c *Cache) GetInstaller(ctx context.Context) (types.Installer, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetInstaller")
defer span.End()
rg, err := c.read()
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
inst, err := rg.clusterConfig.GetInstaller(ctx)
return inst, trace.Wrap(err)
}
// GetRoles is a part of auth.Cache implementation
func (c *Cache) GetRoles(ctx context.Context) ([]types.Role, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetRoles")

View file

@ -2607,6 +2607,7 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) {
types.KindLock: &types.LockV2{},
types.KindWindowsDesktopService: &types.WindowsDesktopServiceV3{},
types.KindWindowsDesktop: &types.WindowsDesktopV3{},
types.KindInstaller: &types.InstallerV1{},
}
for name, cfg := range cases {

View file

@ -88,6 +88,11 @@ func setupCollections(c *Cache, watches []types.WatchKind) (map[resourceKind]col
return nil, trace.BadParameter("missing parameter ClusterConfig")
}
collections[resourceKind] = &sessionRecordingConfig{watch: watch, Cache: c}
case types.KindInstaller:
if c.ClusterConfig == nil {
return nil, trace.BadParameter("missing parameter ClusterConfig")
}
collections[resourceKind] = &installerConfig{watch: watch, Cache: c}
case types.KindUser:
if c.Users == nil {
return nil, trace.BadParameter("missing parameter Users")
@ -2255,6 +2260,68 @@ func (c *sessionRecordingConfig) watchKind() types.WatchKind {
return c.watch
}
type installerConfig struct {
*Cache
watch types.WatchKind
}
func (c *installerConfig) erase(ctx context.Context) error {
if err := c.clusterConfigCache.DeleteInstaller(ctx); err != nil {
if !trace.IsNotFound(err) {
return trace.Wrap(err)
}
}
return nil
}
func (c *installerConfig) fetch(ctx context.Context) (apply func(ctx context.Context) error, err error) {
var noConfig bool
resource, err := c.ClusterConfig.GetInstaller(ctx)
if err != nil {
if !trace.IsNotFound(err) {
return nil, trace.Wrap(err)
}
noConfig = true
}
return func(ctx context.Context) error {
// either zero or one instance exists, so we either erase or
// update, but not both.
if noConfig {
return trace.Wrap(c.erase(ctx))
}
return trace.Wrap(c.clusterConfigCache.SetInstaller(ctx, resource))
}, nil
}
func (c *installerConfig) processEvent(ctx context.Context, event types.Event) error {
switch event.Type {
case types.OpDelete:
err := c.clusterConfigCache.DeleteInstaller(ctx)
if err != nil {
if !trace.IsNotFound(err) {
c.Warningf("Failed to delete resource %v.", err)
return trace.Wrap(err)
}
}
case types.OpPut:
resource, ok := event.Resource.(types.Installer)
if !ok {
return trace.BadParameter("unexpected type %T", event.Resource)
}
if err := c.clusterConfigCache.SetInstaller(ctx, resource); err != nil {
return trace.Wrap(err)
}
default:
c.Warningf("Skipping unsupported event type %v.", event.Type)
}
return nil
}
func (c *installerConfig) watchKind() types.WatchKind {
return c.watch
}
type networkRestrictions struct {
*Cache
watch types.WatchKind

View file

@ -98,3 +98,9 @@ func SetWebConfigHeaders(h http.Header) {
SetStaticFileHeaders(h)
h.Set("Content-Type", "application/javascript")
}
// SetScriptHeaders sets headers for the teleport install script
func SetScriptHeaders(h http.Header) {
SetStaticFileHeaders(h)
h.Set("Content-Type", "text/x-shellscript")
}

View file

@ -3391,6 +3391,7 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
StaticFS: fs,
ClusterFeatures: process.getClusterFeatures(),
ProxySettings: proxySettings,
PublicProxyAddr: process.proxyPublicAddr().Addr,
})
if err != nil {
return trace.Wrap(err)

View file

@ -70,4 +70,11 @@ type ClusterConfiguration interface {
SetClusterNetworkingConfig(context.Context, types.ClusterNetworkingConfig) error
// DeleteClusterNetworkingConfig deletes ClusterNetworkingConfig from the backend.
DeleteClusterNetworkingConfig(ctx context.Context) error
// GetInstaller gets the installer script from the backend
GetInstaller(context.Context) (types.Installer, error)
// SetInstaller sets the installer script in the backend
SetInstaller(context.Context, types.Installer) error
// DeleteInstaller removes the installer script from the backend
DeleteInstaller(context.Context) error
}

77
lib/services/installer.go Normal file
View file

@ -0,0 +1,77 @@
/**
* Copyright 2022 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package services
import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
)
// UnmarshalInstaller unmarshals the installer resource from JSON.
func UnmarshalInstaller(data []byte, opts ...MarshalOption) (types.Installer, error) {
var installer types.InstallerV1
if len(data) == 0 {
return nil, trace.BadParameter("missing resource data")
}
cfg, err := CollectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
if err := utils.FastUnmarshal(data, &installer); err != nil {
return nil, trace.BadParameter(err.Error())
}
if err := installer.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
if cfg.ID != 0 {
installer.SetResourceID(cfg.ID)
}
if !cfg.Expires.IsZero() {
installer.SetExpiry(cfg.Expires)
}
return &installer, nil
}
// MarshalInstaller marshals the Installer resource to JSON.
func MarshalInstaller(installer types.Installer, opts ...MarshalOption) ([]byte, error) {
if err := installer.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
cfg, err := CollectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
switch installer := installer.(type) {
case *types.InstallerV1:
if !cfg.PreserveResourceID {
// avoid modifying the original object
// to prevent unexpected data races
copy := *installer
copy.SetResourceID(0)
installer = &copy
}
return utils.FastMarshal(installer)
default:
return nil, trace.BadParameter("unrecognized installer version %T", installer)
}
}

View file

@ -361,6 +361,36 @@ func (s *ClusterConfigurationService) DeleteSessionRecordingConfig(ctx context.C
return nil
}
// GetInstaller gets the script of the cluster from the backend.
func (s *ClusterConfigurationService) GetInstaller(ctx context.Context) (types.Installer, error) {
item, err := s.Get(ctx, backend.Key(clusterConfigPrefix, installerScriptPrefix))
if err != nil {
return nil, trace.Wrap(err)
}
return services.UnmarshalInstaller(item.Value)
}
// SetInstaller sets the script of the cluster in the backend
func (s *ClusterConfigurationService) SetInstaller(ctx context.Context, ins types.Installer) error {
value, err := services.MarshalInstaller(ins)
if err != nil {
return trace.Wrap(err)
}
_, err = s.Put(ctx, backend.Item{
Key: backend.Key(clusterConfigPrefix, installerScriptPrefix),
Value: value,
Expires: ins.Expiry(),
})
return trace.Wrap(err)
}
// DeleteInstaller sets the installer script to default script in the backend.
func (s *ClusterConfigurationService) DeleteInstaller(ctx context.Context) error {
return trace.Wrap(
s.Delete(ctx, backend.Key(clusterConfigPrefix, installerScriptPrefix)))
}
const (
clusterConfigPrefix = "cluster_configuration"
namePrefix = "name"
@ -371,4 +401,5 @@ const (
auditPrefix = "audit"
networkingPrefix = "networking"
sessionRecordingPrefix = "session_recording"
installerScriptPrefix = "installer_script"
)

View file

@ -136,6 +136,8 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type
parser = newWindowsDesktopServicesParser()
case types.KindWindowsDesktop:
parser = newWindowsDesktopsParser()
case types.KindInstaller:
parser = newInstallerParser()
default:
return nil, trace.BadParameter("watcher on object kind %q is not supported", kind.Kind)
}
@ -1263,6 +1265,39 @@ func (p *windowsDesktopsParser) parse(event backend.Event) (types.Resource, erro
}
}
type installerParser struct {
baseParser
}
func newInstallerParser() *installerParser {
return &installerParser{
baseParser: newBaseParser(backend.Key(clusterConfigPrefix, installerScriptPrefix)),
}
}
func (p *installerParser) parse(event backend.Event) (types.Resource, error) {
switch event.Type {
case types.OpDelete:
h, err := resourceHeader(event, types.KindInstaller, types.V1, 0)
if err != nil {
return nil, trace.Wrap(err)
}
h.SetName(types.MetaNameClusterInstallerScript)
return h, nil
case types.OpPut:
inst, err := services.UnmarshalInstaller(event.Item.Value,
services.WithResourceID(event.Item.ID),
services.WithExpires(event.Item.Expires),
)
if err != nil {
return nil, trace.Wrap(err)
}
return inst, nil
default:
return nil, trace.BadParameter("event %v is not supported", event.Type)
}
}
func resourceHeader(event backend.Event, kind, version string, offset int) (types.Resource, error) {
name, err := base(event.Item.Key, offset)
if err != nil {

View file

@ -70,6 +70,7 @@ func NewPresetEditorRole() types.Role {
types.NewRule(types.KindToken, RW()),
types.NewRule(types.KindConnectionDiagnostic, RW()),
types.NewRule(types.KindDatabaseCertificate, RW()),
types.NewRule(types.KindInstaller, RW()),
},
},
},

View file

@ -173,6 +173,8 @@ func ParseShortcut(in string) (string, error) {
return types.KindWindowsDesktop, nil
case types.KindToken, "tokens":
return types.KindToken, nil
case types.KindInstaller:
return types.KindInstaller, nil
}
return "", trace.BadParameter("unsupported resource: %q - resources should be expressed as 'type/name', for example 'connector/github'", in)
}

View file

@ -42,6 +42,7 @@ import (
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/installers"
apiutils "github.com/gravitational/teleport/api/utils"
apisshutils "github.com/gravitational/teleport/api/utils/sshutils"
"github.com/gravitational/teleport/lib/auth"
@ -72,6 +73,7 @@ import (
lemma_secret "github.com/mailgun/lemma/secret"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/mod/semver"
)
const (
@ -190,6 +192,10 @@ type Config struct {
// ProxySettings allows fetching the current proxy settings.
ProxySettings proxySettingsGetter
// PublicProxyAddr is used to template the public proxy address
// into the installer script responses
PublicProxyAddr string
}
type APIHandler struct {
@ -306,6 +312,10 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
// Unauthenticated access to the message of the day
h.GET("/webapi/motd", httplib.MakeHandler(h.motd))
// Unauthenticated access to retrieving the script used to install
// Teleport
h.GET("/webapi/scripts/installer", httplib.MakeHandler(h.installer))
// DELETE IN: 5.1.0
//
// Migrated this endpoint to /webapi/sessions/web below.
@ -1379,6 +1389,30 @@ func (h *Handler) oidcCallback(w http.ResponseWriter, r *http.Request, p httprou
return redirectURL.String()
}
func (h *Handler) installer(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
httplib.SetScriptHeaders(w.Header())
installer, err := h.auth.proxyClient.GetInstaller(r.Context())
if err != nil {
return nil, trace.Wrap(err)
}
ping, err := h.auth.Ping(r.Context())
if err != nil {
return nil, trace.Wrap(err)
}
// semver parsing requires a 'v' at the beginning of the version string.
version := semver.Major("v" + ping.ServerVersion)
instTmpl, err := template.New("").Parse(installer.GetScript())
if err != nil {
return nil, trace.Wrap(err)
}
tmpl := installers.Template{
PublicProxyAddr: h.cfg.PublicProxyAddr,
MajorVersion: version,
}
err = instTmpl.Execute(w, tmpl)
return nil, trace.Wrap(err)
}
// AuthParams are used to construct redirect URL containing auth
// information back to tsh login
type AuthParams struct {

View file

@ -917,3 +917,20 @@ func (c *kubeServerCollection) writeJSON(w io.Writer) error {
_, err = w.Write(data)
return trace.Wrap(err)
}
type installerCollection struct {
installer types.Installer
}
func (c *installerCollection) resources() (r []types.Resource) {
return []types.Resource{c.installer}
}
func (c *installerCollection) writeText(w io.Writer) error {
t := asciitable.MakeTable([]string{"Script"})
t.AddRow([]string{
c.installer.GetScript(),
})
_, err := t.AsBuffer().WriteTo(w)
return trace.Wrap(err)
}

View file

@ -104,6 +104,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *service.
types.KindApp: rc.createApp,
types.KindDatabase: rc.createDatabase,
types.KindToken: rc.createToken,
types.KindInstaller: rc.createInstaller,
}
rc.config = config
@ -592,12 +593,23 @@ func (rc *ResourceCommand) createToken(ctx context.Context, client auth.ClientI,
return trace.Wrap(err)
}
func (rc *ResourceCommand) createInstaller(ctx context.Context, client auth.ClientI, raw services.UnknownResource) error {
inst, err := services.UnmarshalInstaller(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
err = client.SetInstaller(ctx, inst)
return trace.Wrap(err)
}
// Delete deletes resource by name
func (rc *ResourceCommand) Delete(ctx context.Context, client auth.ClientI) (err error) {
singletonResources := []string{
types.KindClusterAuthPreference,
types.KindClusterNetworkingConfig,
types.KindSessionRecordingConfig,
types.KindInstaller,
}
if !apiutils.SliceContainsStr(singletonResources, rc.ref.Kind) && (rc.ref.Kind == "" || rc.ref.Name == "") {
return trace.BadParameter("provide a full resource name to delete, for example:\n$ tctl rm cluster/east\n")
@ -800,6 +812,12 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client auth.ClientI) (err
return trace.NotFound("kubernetes server %q not found", rc.ref.Name)
}
fmt.Printf("kubernetes server %q has been deleted\n", rc.ref.Name)
case types.KindInstaller:
err := client.DeleteInstaller(ctx)
if err != nil {
return trace.Wrap(err)
}
fmt.Printf("%s has been reset to a default value\n", types.KindInstaller)
default:
return trace.BadParameter("deleting resources of type %q is not supported", rc.ref.Kind)
}
@ -1287,6 +1305,15 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client auth.Client
return nil, trace.Wrap(err)
}
return &tokenCollection{tokens: []types.ProvisionToken{token}}, nil
case types.KindInstaller:
if rc.ref.Name != "" {
return nil, trace.BadParameter("installer is a singleton resource, use `tctl get %v` to fetch it", types.KindInstaller)
}
inst, err := client.GetInstaller(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return &installerCollection{inst}, nil
}
return nil, trace.BadParameter("getting %q is not supported", rc.ref.String())
}