Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16] Machine ID: application-tunnel service #44443

Merged
merged 5 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/tbot/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,12 @@ func (o *ServiceConfigs) UnmarshalYAML(node *yaml.Node) error {
return trace.Wrap(err)
}
out = append(out, v)
case ApplicationTunnelServiceType:
v := &ApplicationTunnelService{}
if err := node.Decode(v); err != nil {
return trace.Wrap(err)
}
out = append(out, v)
default:
return trace.BadParameter("unrecognized service type (%s)", header.Type)
}
Expand Down
5 changes: 5 additions & 0 deletions lib/tbot/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ func TestBotConfig_YAML(t *testing.T) {
Path: "/bot/output",
},
},
&ApplicationTunnelService{
Listen: "tcp://127.0.0.1:123",
Roles: []string{"access"},
AppName: "my-app",
},
},
},
},
Expand Down
83 changes: 83 additions & 0 deletions lib/tbot/config/service_application_tunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Teleport
* Copyright (C) 2024 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 config

import (
"net"
"net/url"

"github.com/gravitational/trace"
"gopkg.in/yaml.v3"
)

var (
_ ServiceConfig = &ApplicationTunnelService{}
)

const ApplicationTunnelServiceType = "application-tunnel"

// ApplicationTunnelService opens an authenticated tunnel for Application
// Access.
type ApplicationTunnelService struct {
// Listen is the address on which database tunnel should listen. Example:
// - "tcp://127.0.0.1:3306"
// - "tcp://0.0.0.0:3306
Listen string `yaml:"listen"`
// Roles is the list of roles to request for the tunnel.
// If empty, it defaults to all the bot's roles.
Roles []string `yaml:"roles,omitempty"`
// AppName should be the name of the application as registered in Teleport
// that you wish to tunnel to.
AppName string `yaml:"app_name"`

// Listener overrides "listen" and directly provides an opened listener to
// use.
Listener net.Listener `yaml:"-"`
}

func (s *ApplicationTunnelService) Type() string {
return ApplicationTunnelServiceType
}

func (s *ApplicationTunnelService) MarshalYAML() (interface{}, error) {
type raw ApplicationTunnelService
return withTypeHeader((*raw)(s), ApplicationTunnelServiceType)
}

func (s *ApplicationTunnelService) UnmarshalYAML(node *yaml.Node) error {
// Alias type to remove UnmarshalYAML to avoid recursion
type raw ApplicationTunnelService
if err := node.Decode((*raw)(s)); err != nil {
return trace.Wrap(err)
}
return nil
}

func (s *ApplicationTunnelService) CheckAndSetDefaults() error {
switch {
case s.Listen == "" && s.Listener == nil:
return trace.BadParameter("listen: should not be empty")
case s.AppName == "":
return trace.BadParameter("app_name: should not be empty")
}
if _, err := url.Parse(s.Listen); err != nil {
return trace.Wrap(err, "parsing listen")
}
return nil
}
86 changes: 86 additions & 0 deletions lib/tbot/config/service_application_tunnel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Teleport
* Copyright (C) 2024 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 config

import "testing"

func TestApplicationTunnelService_YAML(t *testing.T) {
t.Parallel()

tests := []testYAMLCase[ApplicationTunnelService]{
{
name: "full",
in: ApplicationTunnelService{
Listen: "tcp://0.0.0.0:3621",
AppName: "my-app",
},
},
}
testYAML(t, tests)
}

func TestApplicationTunnelService_CheckAndSetDefaults(t *testing.T) {
t.Parallel()

tests := []testCheckAndSetDefaultsCase[*ApplicationTunnelService]{
{
name: "valid",
in: func() *ApplicationTunnelService {
return &ApplicationTunnelService{
Listen: "tcp://0.0.0.0:3621",
Roles: []string{"role1", "role2"},
AppName: "my-app",
}
},
wantErr: "",
},
{
name: "missing listen",
in: func() *ApplicationTunnelService {
return &ApplicationTunnelService{
Roles: []string{"role1", "role2"},
AppName: "my-app",
}
},
wantErr: "listen: should not be empty",
},
{
name: "listen not url",
in: func() *ApplicationTunnelService {
return &ApplicationTunnelService{
Listen: "\x00",
Roles: []string{"role1", "role2"},
AppName: "my-app",
}
},
wantErr: "parsing listen",
},
{
name: "missing app name",
in: func() *ApplicationTunnelService {
return &ApplicationTunnelService{
Listen: "tcp://0.0.0.0:3621",
Roles: []string{"role1", "role2"},
}
},
wantErr: "app_name: should not be empty",
},
}
testCheckAndSetDefaults(t, tests)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type: application-tunnel
listen: tcp://0.0.0.0:3621
app_name: my-app
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ services:
path: /bot/output
enable_resumption: null
proxy_templates_path: ""
- type: application-tunnel
listen: tcp://127.0.0.1:123
roles:
- access
app_name: my-app
debug: true
auth_server: example.teleport.sh:443
certificate_ttl: 1m0s
Expand Down
24 changes: 18 additions & 6 deletions lib/tbot/service_application_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ func (s *ApplicationOutputService) generate(ctx context.Context) error {
}
defer impersonatedClient.Close()

routeToApp, err := getRouteToApp(ctx, s.getBotIdentity(), impersonatedClient, s.cfg)
routeToApp, _, err := getRouteToApp(
ctx,
s.getBotIdentity(),
impersonatedClient,
s.cfg.AppName,
)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -197,13 +202,18 @@ func (s *ApplicationOutputService) render(
return trace.Wrap(writeTLSCAs(ctx, s.cfg.Destination, hostCAs, userCAs, databaseCAs))
}

func getRouteToApp(ctx context.Context, botIdentity *identity.Identity, client *authclient.Client, output *config.ApplicationOutput) (proto.RouteToApp, error) {
func getRouteToApp(
ctx context.Context,
botIdentity *identity.Identity,
client *authclient.Client,
appName string,
) (proto.RouteToApp, types.Application, error) {
ctx, span := tracer.Start(ctx, "getRouteToApp")
defer span.End()

app, err := getApp(ctx, client, output.AppName)
app, err := getApp(ctx, client, appName)
if err != nil {
return proto.RouteToApp{}, trace.Wrap(err)
return proto.RouteToApp{}, nil, trace.Wrap(err)
}

routeToApp := proto.RouteToApp{
Expand All @@ -213,12 +223,14 @@ func getRouteToApp(ctx context.Context, botIdentity *identity.Identity, client *
}

// TODO (Joerger): DELETE IN v17.0.0
// TODO(noah): When this is deleted, we can begin to cache the routeToApp
// rather than regenerating this on each renew in the ApplicationTunnelSvc
routeToApp.SessionID, err = authclient.TryCreateAppSessionForClientCertV15(ctx, client, botIdentity.X509Cert.Subject.CommonName, routeToApp)
if err != nil {
return proto.RouteToApp{}, trace.Wrap(err)
return proto.RouteToApp{}, nil, trace.Wrap(err)
}

return routeToApp, nil
return routeToApp, app, nil
}

func getApp(ctx context.Context, clt *authclient.Client, appName string) (types.Application, error) {
Expand Down
Loading
Loading