Skip to content
Open
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
170 changes: 108 additions & 62 deletions .github/ISSUE_TEMPLATE/testplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,67 +180,114 @@ as well as an upgrade of the previous version of Teleport.

- [ ] Interact with a cluster using `tsh`

These commands should ideally be tested for recording and non-recording modes as they are implemented in a different ways.

- [ ] tsh ssh \<regular-node\>
- [ ] tsh ssh \<node-remote-cluster\>
- [ ] tsh ssh \<agentless-node\>
- [ ] tsh ssh \<agentless-node-remote-cluster\>
- [ ] tsh ssh -A \<regular-node\>
- [ ] tsh ssh -A \<node-remote-cluster\>
- [ ] tsh ssh -A \<agentless-node\>
- [ ] tsh ssh -A \<agentless-node-remote-cluster\>
- [ ] tsh ssh \<regular-node\> ls
- [ ] tsh ssh \<node-remote-cluster\> ls
- [ ] tsh ssh \<agentless-node\> ls
- [ ] tsh ssh \<agentless-node-remote-cluster\> ls
- [ ] tsh join \<regular-node\>
- [ ] tsh join \<node-remote-cluster\>
- [ ] tsh play \<regular-node\>
- [ ] tsh play \<node-remote-cluster\>
- [ ] tsh play \<agentless-node\>
- [ ] tsh play \<agentless-node-remote-cluster\>
- [ ] tsh scp \<regular-node\>
- [ ] tsh scp \<node-remote-cluster\>
- [ ] tsh scp \<agentless-node\>
- [ ] tsh scp \<agentless-node-remote-cluster\>
- [ ] tsh ssh -L \<regular-node\>
- [ ] tsh ssh -L \<node-remote-cluster\>
- [ ] tsh ssh -L \<agentless-node\>
- [ ] tsh ssh -L \<agentless-node-remote-cluster\>
- [ ] tsh ssh -R \<regular-node\>
- [ ] tsh ssh -R \<node-remote-cluster\>
- [ ] tsh ssh -R \<agentless-node\>
- [ ] tsh ssh -R \<agentless-node-remote-cluster\>
- [ ] tsh ls
- [ ] tsh clusters
These commands should ideally be tested for recording and non-recording modes as they are implemented in a different ways.
Recording can be disabled by adding `session_recording: off` to `auth_service` in your config. A regular node refers to
a [Teleport SSH service](https://goteleport.com/docs/enroll-resources/server-access/getting-started/). An agentless node is an [OpenSSH server](https://goteleport.com/docs/enroll-resources/server-access/openssh/openssh-agentless) that has been enrolled into Teleport. A remote cluster is a leaf cluster that is connected to a root cluster via a [trusted cluster setup](https://goteleport.com/docs/admin-guides/management/admin/trustedclusters/). Here's a recommended setup for testing:

```
┌───────────────┐
│ │
┌►│ Regular Node │
┌───────────────┐ ┌───────────────┐ │ │ │
│ │ │ │ │ └───────────────┘
│ Root Cluster ├───►│ Leaf Cluster ├─┤
│ │ │ │ │ ┌───────────────┐
└───────────────┘ └───────────────┘ │ │ │
└►│ OpenSSH Node │
│ │
└───────────────┘
```

When you want to test a non-remote-cluster, use the Leaf Cluster as your proxy target.

- [ ] `tsh ssh <regular-node>`
- [ ] `tsh ssh <node-remote-cluster>`
- [ ] `tsh ssh <agentless-node>`
- [ ] `tsh ssh <agentless-node-remote-cluster>`

Test agent had been forwarded by running `ssh-add -L` and check that your teleport keys are listed. Each cluster requires the `permit-agent-forwarding` flag and the role you're assuming in the leaf cluster needs `Agent Forwarding` enabled. Example connection command:
`tsh ssh -A --proxy $PROXY --cluster $REMOTE_CLUSTER $USER@$NODE_NAME`

- [ ] `tsh ssh -A <regular-node>`
- [ ] `tsh ssh -A <node-remote-cluster>`
- [ ] `tsh ssh -A <agentless-node>`
- [ ] `tsh ssh -A <agentless-node-remote-cluster>`
- [ ] `tsh ssh <regular-node> ls`
- [ ] `tsh ssh <node-remote-cluster> ls`
- [ ] `tsh ssh <agentless-node> ls`
- [ ] `tsh ssh <agentless-node-remote-cluster> ls`
- [ ] `tsh join <regular-node-session-id>`
- [ ] `tsh join <node-remote-cluster-session-id>`

For `tsh play`, ensure the role you assume on the leaf cluster has `read` and `list` for the `session` resource. Example allow rule:
```yaml
spec:
allow:
rules:
- resources:
- session
verbs:
- read
- list
```

- [ ] `tsh play <regular-node-session-id>`
- [ ] `tsh play <node-remote-cluster-session-id>`
- [ ] `tsh play <agentless-node>`
- [ ] `tsh play <agentless-node-remote-cluster>`
- [ ] `tsh scp <regular-node>`
- [ ] `tsh scp <node-remote-cluster>`
- [ ] `tsh scp <agentless-node>`
- [ ] `tsh scp <agentless-node-remote-cluster>`

This forwards the local port to the remote node, test this with a web server running on the remote node, e.g. `python3 -m http.server 8000` on the remote node, setup a tunnel to the node with `tsh ssh -L 9000:localhost:8000 <remote-node>`, then `curl http://localhost:9000` from your local machine.

- [ ] `tsh ssh -L <regular-node>`
- [ ] `tsh ssh -L <node-remote-cluster>`
- [ ] `tsh ssh -L <agentless-node>`
- [ ] `tsh ssh -L <agentless-node-remote-cluster>`

`-R` forwards the remote port to the local machine, test this with a web server running on your local machine, e.g. `python3 -m http.server 8000`, setup a tunnel to the node with `tsh ssh -R 9000:localhost:8000 <remote-node>`, then `curl http://localhost:9000` from the remote node.

- [ ] `tsh ssh -R <regular-node>`
- [ ] `tsh ssh -R <node-remote-cluster>`
- [ ] `tsh ssh -R <agentless-node>`
- [ ] `tsh ssh -R <agentless-node-remote-cluster>`
- [ ] `tsh ls`
- [ ] `tsh clusters`

- [ ] Interact with a cluster using `ssh`
Make sure to test both recording and regular proxy modes.
- [ ] ssh \<regular-node\>
- [ ] ssh \<node-remote-cluster\>
- [ ] ssh \<agentless-node\>
- [ ] ssh \<agentless-node-remote-cluster\>
- [ ] ssh -A \<regular-node\>
- [ ] ssh -A \<node-remote-cluster\>
- [ ] ssh -A \<agentless-node\>
- [ ] ssh -A \<agentless-node-remote-cluster\>
- [ ] ssh \<regular-node\> ls
- [ ] ssh \<node-remote-cluster\> ls
- [ ] ssh \<agentless-node\> ls
- [ ] ssh \<agentless-node-remote-cluster\> ls
- [ ] scp \<regular-node\>
- [ ] scp \<node-remote-cluster\>
- [ ] scp \<agentless-node\>
- [ ] scp \<agentless-node-remote-cluster\>
- [ ] ssh -L \<regular-node\>
- [ ] ssh -L \<node-remote-cluster\>
- [ ] ssh -L \<agentless-node\>
- [ ] ssh -L \<agentless-node-remote-cluster\>
- [ ] ssh -R \<regular-node\>
- [ ] ssh -R \<node-remote-cluster\>
- [ ] ssh -R \<agentless-node\>
- [ ] ssh -R \<agentless-node-remote-cluster\>

Make sure to test both recording and regular proxy modes. Generate an [SSH config](https://goteleport.com/docs/reference/cli/tsh/#tsh-config), one per cluster. An SSH command will look something like this:

`ssh -p 22 -F /path/to/generated/ssh_config <user>@<node-name>.<cluster-that-the-node-is-in>`

To test connecting to a remote cluster, use the root cluster's `ssh_config` and the name of the remote cluster for `<cluster-that-the-node-is-in>`.

- [ ] `ssh <regular-node>`
- [ ] `ssh <node-remote-cluster>`
- [ ] `ssh <agentless-node>`
- [ ] `ssh <agentless-node-remote-cluster>`
- [ ] `ssh -A <regular-node>`
- [ ] `ssh -A <node-remote-cluster>`
- [ ] `ssh -A <agentless-node>`
- [ ] `ssh -A <agentless-node-remote-cluster>`
- [ ] `ssh <regular-node> ls`
- [ ] `ssh <node-remote-cluster> ls`
- [ ] `ssh <agentless-node> ls`
- [ ] `ssh <agentless-node-remote-cluster> ls`
- [ ] `scp <regular-node>`
- [ ] `scp <node-remote-cluster>`
- [ ] `scp <agentless-node>`
- [ ] `scp <agentless-node-remote-cluster>`
- [ ] `ssh -L <regular-node>`
- [ ] `ssh -L <node-remote-cluster>`
- [ ] `ssh -L <agentless-node>`
- [ ] `ssh -L <agentless-node-remote-cluster>`
- [ ] `ssh -R <regular-node>`
- [ ] `ssh -R <node-remote-cluster>`
- [ ] `ssh -R <agentless-node>`
- [ ] `ssh -R <agentless-node-remote-cluster>`

- [ ] Verify proxy jump functionality
Log into leaf cluster via root, shut down the root proxy and verify proxy jump works.
Expand Down Expand Up @@ -1177,16 +1224,15 @@ release/dev build. If you are building Teleport Connect in development mode, you
config option `hardwareKeyAgent.enabled: true` and restart Connect. You can run a non-login `tsh`
command to check if the agent is running.

Before logging in to Teleport Connect:
In `tsh`, without logging into Teleport Connect:

- [ ] `tsh login` prompts for PIV PIN and touch without using the Hardware Key Agent
- [ ] All other `tsh` commands prompt for PIN and touch via the Hardware Key Agent
- [ ] Test a subset of the `tsh` commands from the test above
- [ ] The command is displayed in the PIN and touch prompts
- [ ] Connecting with OpenSSH `ssh` prompts for PIN and touch via the hardware key agent
- [ ] The PIN is cached for the configured duration between basic `tsh` commands (set `pin_cache_ttl` to something longer that 15s if needed)

After logging in to Teleport Connect:
In Teleport Connect:

- [ ] Login prompts for PIN and touch
- [ ] Server Access
Expand Down
67 changes: 67 additions & 0 deletions api/types/sessionrecording.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package types

import (
"iter"
"slices"
"strings"
"time"
Expand All @@ -43,6 +44,16 @@ type SessionRecordingConfig interface {
// SetProxyChecksHostKeys sets if the proxy will check host keys.
SetProxyChecksHostKeys(bool)

// GetEncrypted gets if session recordings should be encrypted or not.
GetEncrypted() bool

// GetEncryptionKeys gets the encryption keys for the session recording config.
GetEncryptionKeys() []*AgeEncryptionKey

// SetEncryptionKeys sets the encryption keys for the session recording config.
// It returns true if there was a change applied and false otherwise.
SetEncryptionKeys(iter.Seq[*AgeEncryptionKey]) bool

// Clone returns a copy of the resource.
Clone() SessionRecordingConfig
}
Expand Down Expand Up @@ -163,6 +174,62 @@ func (c *SessionRecordingConfigV2) SetProxyChecksHostKeys(t bool) {
c.Spec.ProxyChecksHostKeys = NewBoolOption(t)
}

// GetEncrypted gets if session recordings should be encrypted or not.
func (c *SessionRecordingConfigV2) GetEncrypted() bool {
encryption := c.Spec.Encryption
if encryption == nil {
return false
}

return encryption.Enabled
}

// GetEncryptionKeys gets the encryption keys for the session recording config.
func (c *SessionRecordingConfigV2) GetEncryptionKeys() []*AgeEncryptionKey {
if c.Status != nil {
return c.Status.EncryptionKeys
}

return nil
}

// SetEncryptionKeys sets the encryption keys for the session recording config.
// It returns true if there was a change applied and false otherwise.
func (c *SessionRecordingConfigV2) SetEncryptionKeys(keys iter.Seq[*AgeEncryptionKey]) bool {
existingKeys := make(map[string]struct{})
for _, key := range c.GetEncryptionKeys() {
existingKeys[string(key.PublicKey)] = struct{}{}
}

var keysChanged bool
var newKeys []*AgeEncryptionKey
addedKeys := make(map[string]struct{})
for key := range keys {
if !keysChanged {
if _, exists := existingKeys[string(key.PublicKey)]; !exists {
keysChanged = true
}
}

if _, added := addedKeys[string(key.PublicKey)]; !added {
addedKeys[string(key.PublicKey)] = struct{}{}
newKeys = append(newKeys, key)
}

}

if !keysChanged || len(newKeys) == 0 || len(existingKeys) == len(addedKeys) {
return false
}

if c.Status == nil {
c.Status = &SessionRecordingConfigStatus{}
}
c.Status.EncryptionKeys = newKeys

return true
}

// Clone returns a copy of the resource.
func (c *SessionRecordingConfigV2) Clone() SessionRecordingConfig {
return utils.CloneProtoMsg(c)
Expand Down
26 changes: 0 additions & 26 deletions api/utils/keys/alias.go

This file was deleted.

45 changes: 0 additions & 45 deletions api/utils/keys/policy_piv.go

This file was deleted.

7 changes: 6 additions & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,12 @@
},
{
"source": "/enroll-resources/workload-identity/workload-attestation/",
"destination": "/machine-workload-identity/workload-identity/workload-attestation/",
"destination": "/reference/workload-identity/workload-identity-api-and-workload-attestation/",
"permanent": true
},
{
"source": "/machine-workload-identity/workload-identity/workload-attestation/",
"destination": "/reference/workload-identity/workload-identity-api-and-workload-attestation/",
"permanent": true
},
{
Expand Down
Loading