Skip to content
Snippets Groups Projects
Commit 5d7e2397 authored by Niall Sheridan's avatar Niall Sheridan
Browse files

Add critical options support

parent 57224ffa
Branches
Tags
No related merge requests found
...@@ -219,7 +219,7 @@ Supported options: ...@@ -219,7 +219,7 @@ Supported options:
- `signing_key`: string. Path to the signing ssh private key you created earlier. See the [note](#a-note-on-files) on files above. - `signing_key`: string. Path to the signing ssh private key you created earlier. See the [note](#a-note-on-files) on files above.
- `additional_principals`: array of string. By default certificates will have one principal set - the username portion of the requester's email address. If `additional_principals` is set, these will be added to the certificate e.g. if your production machines use shared user accounts. - `additional_principals`: array of string. By default certificates will have one principal set - the username portion of the requester's email address. If `additional_principals` is set, these will be added to the certificate e.g. if your production machines use shared user accounts.
- `max_age`: string. If set the server will not issue certificates with an expiration value longer than this, regardless of what the client requests. Must be a valid Go [`time.Duration`](https://golang.org/pkg/time/#ParseDuration) string. - `max_age`: string. If set the server will not issue certificates with an expiration value longer than this, regardless of what the client requests. Must be a valid Go [`time.Duration`](https://golang.org/pkg/time/#ParseDuration) string.
- `permissions`: array of string. Actions the certificate can perform. See the [`-O` option to `ssh-keygen(1)`](http://man.openbsd.org/OpenBSD-current/man1/ssh-keygen.1) for a complete list. - `permissions`: array of string. Specify the actions the certificate can perform. See the [`-O` option to `ssh-keygen(1)`](http://man.openbsd.org/OpenBSD-current/man1/ssh-keygen.1) for a complete list. e.g. `permissions = ["permit-pty", "permit-port-forwarding", force-command=/bin/ls", "source-address=192.168.0.0/24"]`
## aws ## aws
AWS configuration is only needed for accessing signing keys stored on S3, and isn't totally necessary even then. AWS configuration is only needed for accessing signing keys stored on S3, and isn't totally necessary even then.
......
...@@ -29,7 +29,7 @@ ssh { ...@@ -29,7 +29,7 @@ ssh {
signing_key = "signing_key" # Path to the CA signing secret key signing_key = "signing_key" # Path to the CA signing secret key
additional_principals = ["ec2-user", "ubuntu"] # Additional principals to allow additional_principals = ["ec2-user", "ubuntu"] # Additional principals to allow
max_age = "720h" # Maximum lifetime of a ssh certificate max_age = "720h" # Maximum lifetime of a ssh certificate
permissions = ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc"] # Permissions associated with a certificate permissions = ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc", "force-command=/bin/ls"] # Permissions associated with a certificate
} }
# Optional AWS config. if an aws config is present, then files (e.g. signing key or tls cert) can be read from S3 using the syntax `/s3/bucket/path/to/signing.key`. # Optional AWS config. if an aws config is present, then files (e.g. signing key or tls cert) can be read from S3 using the syntax `/s3/bucket/path/to/signing.key`.
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"log" "log"
"strings"
"time" "time"
"go4.org/wkfs" "go4.org/wkfs"
...@@ -16,12 +17,38 @@ import ( ...@@ -16,12 +17,38 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
var (
defaultPermissions = map[string]string{
"permit-X11-forwarding": "",
"permit-agent-forwarding": "",
"permit-port-forwarding": "",
"permit-pty": "",
"permit-user-rc": "",
}
)
// KeySigner does the work of signing a ssh public key with the CA key. // KeySigner does the work of signing a ssh public key with the CA key.
type KeySigner struct { type KeySigner struct {
ca ssh.Signer ca ssh.Signer
validity time.Duration validity time.Duration
principals []string principals []string
permissions map[string]string permissions []string
}
func (s *KeySigner) setPermissions(cert *ssh.Certificate) {
cert.CriticalOptions = make(map[string]string)
cert.Extensions = make(map[string]string)
for _, perm := range s.permissions {
if strings.Contains(perm, "=") {
opt := strings.Split(perm, "=")
cert.CriticalOptions[strings.TrimSpace(opt[0])] = strings.TrimSpace(opt[1])
} else {
cert.Extensions[perm] = ""
}
}
if len(cert.Extensions) == 0 {
cert.Extensions = defaultPermissions
}
} }
// SignUserKey returns a signed ssh certificate. // SignUserKey returns a signed ssh certificate.
...@@ -38,12 +65,12 @@ func (s *KeySigner) SignUserKey(req *lib.SignRequest, username string) (*ssh.Cer ...@@ -38,12 +65,12 @@ func (s *KeySigner) SignUserKey(req *lib.SignRequest, username string) (*ssh.Cer
CertType: ssh.UserCert, CertType: ssh.UserCert,
Key: pubkey, Key: pubkey,
KeyId: fmt.Sprintf("%s_%d", username, time.Now().UTC().Unix()), KeyId: fmt.Sprintf("%s_%d", username, time.Now().UTC().Unix()),
ValidBefore: uint64(req.ValidUntil.Unix()),
ValidAfter: uint64(time.Now().UTC().Add(-5 * time.Minute).Unix()), ValidAfter: uint64(time.Now().UTC().Add(-5 * time.Minute).Unix()),
ValidBefore: uint64(req.ValidUntil.Unix()),
ValidPrincipals: []string{username},
} }
cert.ValidPrincipals = append(cert.ValidPrincipals, username)
cert.ValidPrincipals = append(cert.ValidPrincipals, s.principals...) cert.ValidPrincipals = append(cert.ValidPrincipals, s.principals...)
cert.Extensions = s.permissions s.setPermissions(cert)
if err := cert.SignCert(rand.Reader, s.ca); err != nil { if err := cert.SignCert(rand.Reader, s.ca); err != nil {
return nil, err return nil, err
} }
...@@ -67,23 +94,6 @@ func (s *KeySigner) GenerateRevocationList(certs []*store.CertRecord) ([]byte, e ...@@ -67,23 +94,6 @@ func (s *KeySigner) GenerateRevocationList(certs []*store.CertRecord) ([]byte, e
return k.Marshal(rand.Reader) return k.Marshal(rand.Reader)
} }
func makeperms(perms []string) map[string]string {
if len(perms) > 0 {
m := make(map[string]string)
for _, p := range perms {
m[p] = ""
}
return m
}
return map[string]string{
"permit-X11-forwarding": "",
"permit-agent-forwarding": "",
"permit-port-forwarding": "",
"permit-pty": "",
"permit-user-rc": "",
}
}
// New creates a new KeySigner from the supplied configuration. // New creates a new KeySigner from the supplied configuration.
func New(conf *config.SSH) (*KeySigner, error) { func New(conf *config.SSH) (*KeySigner, error) {
data, err := wkfs.ReadFile(conf.SigningKey) data, err := wkfs.ReadFile(conf.SigningKey)
...@@ -102,6 +112,6 @@ func New(conf *config.SSH) (*KeySigner, error) { ...@@ -102,6 +112,6 @@ func New(conf *config.SSH) (*KeySigner, error) {
ca: key, ca: key,
validity: validity, validity: validity,
principals: conf.AdditionalPrincipals, principals: conf.AdditionalPrincipals,
permissions: makeperms(conf.Permissions), permissions: conf.Permissions,
}, nil }, nil
} }
...@@ -20,6 +20,7 @@ var ( ...@@ -20,6 +20,7 @@ var (
ca: key, ca: key,
validity: 12 * time.Hour, validity: 12 * time.Hour,
principals: []string{"ec2-user"}, principals: []string{"ec2-user"},
permissions: []string{"permit-pty", "force-command=/bin/ls"},
} }
) )
...@@ -79,3 +80,28 @@ func TestRevocationList(t *testing.T) { ...@@ -79,3 +80,28 @@ func TestRevocationList(t *testing.T) {
t.Errorf("cert %s should not be revoked", cert2.KeyId) t.Errorf("cert %s should not be revoked", cert2.KeyId)
} }
} }
func TestPermissions(t *testing.T) {
t.Parallel()
r := &lib.SignRequest{
Key: string(testdata.Pub),
ValidUntil: time.Now().Add(1 * time.Hour),
}
cert, err := signer.SignUserKey(r, "gopher1")
if err != nil {
t.Error(err)
}
want := struct {
extensions map[string]string
options map[string]string
}{
extensions: map[string]string{"permit-pty": ""},
options: map[string]string{"force-command": "/bin/ls"},
}
if !reflect.DeepEqual(cert.Extensions, want.extensions) {
t.Errorf("Wrong permissions: wanted: %v got :%v", cert.Extensions, want.extensions)
}
if !reflect.DeepEqual(cert.CriticalOptions, want.options) {
t.Errorf("Wrong options: wanted: %v got :%v", cert.CriticalOptions, want.options)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment