aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Sheridan <nsheridan@gmail.com>2017-01-10 22:51:28 +0000
committerNiall Sheridan <nsheridan@gmail.com>2017-01-14 01:15:09 +0000
commit5d7e2397226cd4c88a18658d8fc89ca0da58cc49 (patch)
tree61fa7ddd20793b8bea1c40db63461d4edd737851
parent57224ffa79aac59155a0f6a4ad47f224cac736fd (diff)
Add critical options support
-rw-r--r--README.md2
-rw-r--r--example-server.conf2
-rw-r--r--server/signer/signer.go62
-rw-r--r--server/signer/signer_test.go32
4 files changed, 67 insertions, 31 deletions
diff --git a/README.md b/README.md
index bf2e5fc..60a1961 100644
--- a/README.md
+++ b/README.md
@@ -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.
- `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.
-- `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 configuration is only needed for accessing signing keys stored on S3, and isn't totally necessary even then.
diff --git a/example-server.conf b/example-server.conf
index 9a20c9d..8d299fa 100644
--- a/example-server.conf
+++ b/example-server.conf
@@ -29,7 +29,7 @@ ssh {
signing_key = "signing_key" # Path to the CA signing secret key
additional_principals = ["ec2-user", "ubuntu"] # Additional principals to allow
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`.
diff --git a/server/signer/signer.go b/server/signer/signer.go
index a4cf919..2a15849 100644
--- a/server/signer/signer.go
+++ b/server/signer/signer.go
@@ -4,6 +4,7 @@ import (
"crypto/rand"
"fmt"
"log"
+ "strings"
"time"
"go4.org/wkfs"
@@ -16,12 +17,38 @@ import (
"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.
type KeySigner struct {
ca ssh.Signer
validity time.Duration
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.
@@ -35,15 +62,15 @@ func (s *KeySigner) SignUserKey(req *lib.SignRequest, username string) (*ssh.Cer
req.ValidUntil = expires
}
cert := &ssh.Certificate{
- CertType: ssh.UserCert,
- Key: pubkey,
- 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()),
+ CertType: ssh.UserCert,
+ Key: pubkey,
+ KeyId: fmt.Sprintf("%s_%d", username, time.Now().UTC().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.Extensions = s.permissions
+ s.setPermissions(cert)
if err := cert.SignCert(rand.Reader, s.ca); err != nil {
return nil, err
}
@@ -67,23 +94,6 @@ func (s *KeySigner) GenerateRevocationList(certs []*store.CertRecord) ([]byte, e
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.
func New(conf *config.SSH) (*KeySigner, error) {
data, err := wkfs.ReadFile(conf.SigningKey)
@@ -102,6 +112,6 @@ func New(conf *config.SSH) (*KeySigner, error) {
ca: key,
validity: validity,
principals: conf.AdditionalPrincipals,
- permissions: makeperms(conf.Permissions),
+ permissions: conf.Permissions,
}, nil
}
diff --git a/server/signer/signer_test.go b/server/signer/signer_test.go
index baf00e5..3bbdbf9 100644
--- a/server/signer/signer_test.go
+++ b/server/signer/signer_test.go
@@ -17,9 +17,10 @@ import (
var (
key, _ = ssh.ParsePrivateKey(testdata.Priv)
signer = &KeySigner{
- ca: key,
- validity: 12 * time.Hour,
- principals: []string{"ec2-user"},
+ ca: key,
+ validity: 12 * time.Hour,
+ principals: []string{"ec2-user"},
+ permissions: []string{"permit-pty", "force-command=/bin/ls"},
}
)
@@ -79,3 +80,28 @@ func TestRevocationList(t *testing.T) {
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)
+ }
+}