Skip to content

Commit

Permalink
Merge branch 'master' into mvbrock/azure-integration-disco-azure
Browse files Browse the repository at this point in the history
  • Loading branch information
mvbrock authored Dec 19, 2024
2 parents a152fbd + a3f0fdb commit 3c499bb
Show file tree
Hide file tree
Showing 51 changed files with 659 additions and 411 deletions.
12 changes: 10 additions & 2 deletions docs/pages/admin-guides/api/rbac.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,11 @@ spec:
enabled: true
max_session_ttl: 30h0m0s
pin_source_ip: false
port_forwarding: true
ssh_port_forwarding:
remote:
enabled: true
local:
enabled: true
record_session:
default: best_effort
desktop: true
Expand Down Expand Up @@ -906,7 +910,11 @@ spec:
enabled: true
max_session_ttl: 30h0m0s
pin_source_ip: false
port_forwarding: true
ssh_port_forwarding:
remote:
enabled: true
local:
enabled: true
record_session:
default: best_effort
desktop: true
Expand Down
14 changes: 12 additions & 2 deletions docs/pages/enroll-resources/server-access/rbac.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,18 @@ spec:
create_host_user_mode: keep
# forward_agent controls whether SSH agent forwarding is allowed
forward_agent: true
# port_forwarding controls whether TCP port forwarding is allowed for SSH
port_forwarding: true
# ssh_port_forwarding controls which TCP port forwarding modes are allowed over SSH. This replaces
# the deprecated port_forwarding field, which did not differentiate between remote and local
# port forwarding modes. If you have any existing roles that allow forwarding by enabling the
# legacy port_forwarding field then the forwarding controls configured in ssh_port_forwarding will be
# ignored.
ssh_port_forwarding:
# configures remote port forwarding behavior
remote:
enabled: true
# configures local port forwarding behavior
local:
enabled: true
# ssh_file_copy controls whether file copying (SCP/SFTP) is allowed.
# Defaults to true.
ssh_file_copy: false
Expand Down
14 changes: 12 additions & 2 deletions docs/pages/includes/role-spec.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ spec:
max_session_ttl: 8h
# forward_agent controls whether SSH agent forwarding is allowed
forward_agent: true
# port_forwarding controls whether TCP port forwarding is allowed for SSH
port_forwarding: true
# ssh_port_forwarding controls which TCP port forwarding modes are allowed over SSH. This replaces
# the deprecated port_forwarding field, which did not differentiate between remote and local
# port forwarding modes. If you have any existing roles that allow forwarding by enabling the
# legacy port_forwarding field then the forwarding controls configured in ssh_port_forwarding will be
# ignored.
ssh_port_forwarding:
# configures remote port forwarding behavior
remote:
enabled: true
# configures local port forwarding behavior
local:
enabled: true
# ssh_file_copy controls whether file copying (SCP/SFTP) is allowed.
# Defaults to true.
ssh_file_copy: false
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/reference/access-controls/roles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ user:
| - | - | - |
| `max_session_ttl` | Max. time to live (TTL) of a user's SSH certificates | The shortest TTL wins |
| `forward_agent` | Allow SSH agent forwarding | Logical "OR" i.e. if any role allows agent forwarding, it's allowed |
| `port_forwarding` | Allow TCP port forwarding | Logical "OR" i.e. if any role allows port forwarding, it's allowed |
| `ssh_port_forwarding` | Allow TCP port forwarding | Logical "AND" i.e. if any role denies port forwarding, it's denied |
| `ssh_file_copy` | Allow SCP/SFTP | Logical "AND" i.e. if all roles allows file copying, it's allowed |
| `client_idle_timeout` | Forcefully terminate active sessions after an idle interval | The shortest timeout value wins, i.e. the most restrictive value is selected |
| `disconnect_expired_cert` | Forcefully terminate active sessions when a client certificate expires | Logical "OR" i.e. evaluates to "yes" if at least one role requires session termination |
Expand Down
14 changes: 11 additions & 3 deletions docs/pages/reference/terraform-provider/resources/role.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ resource "teleport_role" "example" {
spec = {
options = {
forward_agent = false
max_session_ttl = "7m"
port_forwarding = false
forward_agent = false
max_session_ttl = "7m"
ssh_port_forwarding = {
remote = {
enabled = false
}
local = {
enabled = false
}
}
client_idle_timeout = "1h"
disconnect_expired_cert = true
permit_x11_forwarding = false
Expand Down
6 changes: 5 additions & 1 deletion examples/resources/admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ spec:
- network
forward_agent: true
max_session_ttl: 30h0m0s
port_forwarding: true
ssh_port_forwarding:
remote:
enabled: true
local:
enabled: true
version: v3
6 changes: 5 additions & 1 deletion examples/resources/user.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,9 @@ spec:
- network
forward_agent: true
max_session_ttl: 30h0m0s
port_forwarding: true
ssh_port_forwarding:
remote:
enabled: true
local:
enabled: true
version: v3
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ resource "teleport_role" "example" {

spec = {
options = {
forward_agent = false
max_session_ttl = "7m"
port_forwarding = false
forward_agent = false
max_session_ttl = "7m"
ssh_port_forwarding = {
remote = {
enabled = false
}

local = {
enabled = false
}
}
client_idle_timeout = "1h"
disconnect_expired_cert = true
permit_x11_forwarding = false
Expand Down
38 changes: 36 additions & 2 deletions integrations/terraform/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2051,7 +2051,8 @@ Options is for OpenSSH options like agent forwarding.
| max_sessions | number | | MaxSessions defines the maximum number of concurrent sessions per connection. |
| permit_x11_forwarding | bool | | PermitX11Forwarding authorizes use of X11 forwarding. |
| pin_source_ip | bool | | PinSourceIP forces the same client IP for certificate generation and usage |
| port_forwarding | bool | | |
| ssh_port_forwarding | object | | SSHPortForwarding configures what types of SSH port forwarding are allowed by a role. |
| port_forwarding | bool | | Deprecated: Use SSHPortForwarding instead. |
| record_session | object | | RecordDesktopSession indicates whether desktop access sessions should be recorded. It defaults to true unless explicitly set to false. |
| request_access | string | | RequestAccess defines the access request strategy (optional|note|always) where optional is the default. |
| request_prompt | string | | RequestPrompt is an optional message which tells users what they aught to request. |
Expand Down Expand Up @@ -2085,6 +2086,31 @@ SAML are options related to the Teleport SAML IdP.
|---------|------|----------|-------------|
| enabled | bool | | |

##### spec.options.ssh_port_forwarding

SSHPortForwarding configures what types of SSH port forwarding are allowed by a role.

| Name | Type | Required | Description |
|--------|--------|----------|-----------------------------------------------------------|
| remote | object | | remote contains options related to remote port forwarding |
| local | object | | local contains options related to local port forwarding |

###### spec.options.ssh_port_forwarding.remote

remote contains options related to remote port forwarding

| Name | Type | Required | Description |
|---------|------|----------|-------------|
| enabled | bool | | |

###### spec.options.ssh_port_forwarding.local

local contains options related to local port forwarding

| Name | Type | Required | Description |
|---------|------|----------|-------------|
| enabled | bool | | |

##### spec.options.record_session

RecordDesktopSession indicates whether desktop access sessions should be recorded. It defaults to true unless explicitly set to false.
Expand Down Expand Up @@ -2114,11 +2140,19 @@ resource "teleport_role" "example" {
options = {
forward_agent = false
max_session_ttl = "7m"
port_forwarding = false
client_idle_timeout = "1h"
disconnect_expired_cert = true
permit_x11_forwarding = false
request_access = "denied"
ssh_port_forwarding = {
remote = {
enabled = false
}
local = {
enabled = false
}
}
}
allow = {
Expand Down
42 changes: 36 additions & 6 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1508,12 +1508,11 @@ func (a *Server) runPeriodicOperations() {
heartbeatsMissedByAuth.Inc()
}

if srv.GetSubKind() != types.SubKindOpenSSHNode {
return false, nil
}
// TODO(tross) DELETE in v20.0.0 - all invalid hostnames should have been sanitized by then.
if !validServerHostname(srv.GetHostname()) {
if srv.GetSubKind() != types.SubKindOpenSSHNode {
return false, nil
}

logger := a.logger.With("server", srv.GetName(), "hostname", srv.GetHostname())

logger.DebugContext(a.closeCtx, "sanitizing invalid static SSH server hostname")
Expand All @@ -1527,6 +1526,17 @@ func (a *Server) runPeriodicOperations() {
if _, err := a.Services.UpdateNode(a.closeCtx, srv); err != nil && !trace.IsCompareFailed(err) {
logger.WarnContext(a.closeCtx, "failed to update SSH server hostname", "error", err)
}
} else if oldHostname, ok := srv.GetLabel(replacedHostnameLabel); ok && validServerHostname(oldHostname) {
// If the hostname has been replaced by a sanitized version, revert it back to the original
// if the original is valid under the most recent rules.
logger := a.logger.With("server", srv.GetName(), "old_hostname", oldHostname, "sanitized_hostname", srv.GetHostname())
if err := restoreSanitizedHostname(srv); err != nil {
logger.WarnContext(a.closeCtx, "failed to restore sanitized static SSH server hostname", "error", err)
return false, nil
}
if _, err := a.Services.UpdateNode(a.closeCtx, srv); err != nil && !trace.IsCompareFailed(err) {
log.Warnf("Failed to update node hostname: %v", err)
}
}

return false, nil
Expand Down Expand Up @@ -5650,15 +5660,15 @@ func (a *Server) KeepAliveServer(ctx context.Context, h types.KeepAlive) error {

const (
serverHostnameMaxLen = 256
serverHostnameRegexPattern = `^[a-zA-Z0-9]([\.-]?[a-zA-Z0-9]+)*$`
serverHostnameRegexPattern = `^[a-zA-Z0-9]+[a-zA-Z0-9\.-]*$`
replacedHostnameLabel = types.TeleportInternalLabelPrefix + "invalid-hostname"
)

var serverHostnameRegex = regexp.MustCompile(serverHostnameRegexPattern)

// validServerHostname returns false if the hostname is longer than 256 characters or
// does not entirely consist of alphanumeric characters as well as '-' and '.'. A valid hostname also
// cannot begin with a symbol, and a symbol cannot be followed immediately by another symbol.
// cannot begin with a symbol.
func validServerHostname(hostname string) bool {
return len(hostname) <= serverHostnameMaxLen && serverHostnameRegex.MatchString(hostname)
}
Expand Down Expand Up @@ -5697,6 +5707,26 @@ func sanitizeHostname(server types.Server) error {
return nil
}

// restoreSanitizedHostname restores the original hostname of a server and removes the label.
func restoreSanitizedHostname(server types.Server) error {
oldHostname, ok := server.GetLabels()[replacedHostnameLabel]
// if the label is not present or the hostname is invalid under the most recent rules, do nothing.
if !ok || !validServerHostname(oldHostname) {
return nil
}

switch s := server.(type) {
case *types.ServerV2:
// restore the original hostname and remove the label.
s.Spec.Hostname = oldHostname
delete(s.Metadata.Labels, replacedHostnameLabel)
default:
return trace.BadParameter("invalid server provided")
}

return nil
}

// UpsertNode implements [services.Presence] by delegating to [Server.Services]
// and potentially emitting a [usagereporter] event.
func (a *Server) UpsertNode(ctx context.Context, server types.Server) (*types.KeepAlive, error) {
Expand Down
80 changes: 75 additions & 5 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4478,6 +4478,10 @@ func TestServerHostnameSanitization(t *testing.T) {
name: "uuid dns hostname",
hostname: uuid.NewString() + ".example.com",
},
{
name: "valid dns hostname with multi-dots",
hostname: "llama..example.com",
},
{
name: "empty hostname",
hostname: "",
Expand All @@ -4488,11 +4492,6 @@ func TestServerHostnameSanitization(t *testing.T) {
hostname: strings.Repeat("a", serverHostnameMaxLen*2),
invalidHostname: true,
},
{
name: "invalid dns hostname",
hostname: "llama..example.com",
invalidHostname: true,
},
{
name: "spaces in hostname",
hostname: "the quick brown fox jumps over the lazy dog",
Expand Down Expand Up @@ -4562,3 +4561,74 @@ func TestServerHostnameSanitization(t *testing.T) {
})
}
}

func TestValidServerHostname(t *testing.T) {
t.Parallel()
tests := []struct {
name string
hostname string
want bool
}{
{
name: "valid dns hostname",
hostname: "llama.example.com",
want: true,
},
{
name: "valid friendly hostname",
hostname: "llama",
want: true,
},
{
name: "uuid hostname",
hostname: uuid.NewString(),
want: true,
},
{
name: "valid hostname with multi-dashes",
hostname: "llama--example.com",
want: true,
},
{
name: "valid hostname with multi-dots",
hostname: "llama..example.com",
want: true,
},
{
name: "valid hostname with numbers",
hostname: "llama9",
want: true,
},
{
name: "hostname with invalid characters",
hostname: "llama?!$",
want: false,
},
{
name: "super long hostname",
hostname: strings.Repeat("a", serverHostnameMaxLen*2),
want: false,
},
{
name: "hostname with spaces",
hostname: "the quick brown fox jumps over the lazy dog",
want: false,
},
{
name: "hostname with ;",
hostname: "llama;example.com",
want: false,
},
{
name: "empty hostname",
hostname: "",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := validServerHostname(tt.hostname)
require.Equal(t, tt.want, got)
})
}
}
Loading

0 comments on commit 3c499bb

Please sign in to comment.