Documentation
¶
Overview ¶
Package sshcert handles the domain-grant extension of SSH user certificates, ssh-domain-grant@core.aai.geant.org, from the SSH CA profile of GÉANT's Core AAI platform that the EuroHPC federation (MyAccessID) uses.
A certificate issued under that profile carries one principal, the user's MyAccessID identifier, and in this extension the list of host names, possibly with wildcards, it may be used on:
["*.lumi.example.net","login.example.org"]
A site trusting the CA then refuses, in sshd's AuthorizedPrincipalsCommand, a certificate that was not granted for it. The command cmd/sshcert-authorize does that, and refuses as well a certificate with no grant at all (see its documentation for why).
// Issuing
cert := &ssh.Certificate{ /* ... */ }
if err := sshcert.SetDomainGrant(cert, []string{"*.example.org"}); err != nil { ... }
cert.SignCert(rand.Reader, ca)
// Checking
patterns, present, err := sshcert.DomainGrant(cert)
if !present || err != nil || !sshcert.Grants(patterns, "login.example.org") {
// refuse
}
The specification ¶
The extension is specified in docs/ssh-domain-grant-ext.md of GÉANT's ssh-cert-tool (https://gitlab.geant.org/core-aai-platform/ssh-cert-tool). Its data field is an SSH string holding a compact JSON array of domain patterns, where '*' "matches one or more characters within a single label". This package writes exactly the specification's test vectors and is strict in what it reads (ParseDomainGrant); MatchDomain follows the specification's wording where GÉANT's own matcher does not (see the README).
Index ¶
- Constants
- Variables
- func DomainGrant(cert *ssh.Certificate) (patterns []string, present bool, err error)
- func DomainGrantNamed(cert *ssh.Certificate, name string) (patterns []string, present bool, err error)
- func EncodeDomainGrant(patterns []string) (string, error)
- func Grants(patterns []string, host string) bool
- func MatchDomain(pattern, host string) bool
- func ParseDomainGrant(value string) ([]string, error)
- func SetDomainGrant(cert *ssh.Certificate, patterns []string) error
- func ValidatePattern(p string) error
Constants ¶
const ( // MaxPatternLength is the longest pattern accepted, in bytes: the // longest DNS name in its dotted text form. MaxPatternLength = 253 // MaxLabelLength is the longest label of a pattern, in bytes. MaxLabelLength = 63 )
Limits on a domain pattern. A pattern is a DNS name, possibly with wildcards, so it is held to the lengths of RFC 1035 section 2.3.4.
const DomainGrantExtension = "ssh-domain-grant@core.aai.geant.org"
DomainGrantExtension is the name of the certificate extension that lists the hosts a certificate may be used on, as GÉANT's specification (docs/ssh-domain-grant-ext.md in ssh-cert-tool) and GÉANT's tools spell it. The EuroHPC documentation writes "ssh-domain-grant@core.aai.org"; certificates carry this name.
const MaxGrantLength = 2 + MaxPatterns*(MaxPatternLength+3)
MaxGrantLength is the longest extension value accepted, in bytes: enough for MaxPatterns patterns of MaxPatternLength bytes, quoted and separated.
const MaxPatterns = 64
MaxPatterns is the largest number of patterns a domain grant may hold.
Variables ¶
var ErrInvalidGrant = errors.New("sshcert: invalid domain grant")
ErrInvalidGrant is wrapped by every error about a malformed extension value.
var ErrInvalidPattern = errors.New("sshcert: invalid domain pattern")
ErrInvalidPattern is wrapped by every error ValidatePattern returns.
Functions ¶
func DomainGrant ¶
func DomainGrant(cert *ssh.Certificate) (patterns []string, present bool, err error)
DomainGrant returns the patterns of cert's DomainGrantExtension, and whether the extension is present. An extension that is present but does not parse gives present == true and an error: a caller deciding access must refuse then, never read it as absent.
func DomainGrantNamed ¶
func DomainGrantNamed(cert *ssh.Certificate, name string) (patterns []string, present bool, err error)
DomainGrantNamed is DomainGrant for an extension of another name.
func EncodeDomainGrant ¶
EncodeDomainGrant returns the value to store under DomainGrantExtension in ssh.Certificate.Permissions.Extensions for patterns: their compact JSON array, `["a.example.org","*.example.org"]`, `[]` for none.
The value is the JSON text itself, with no length prefix: golang.org/x/crypto/ssh writes a non-empty extension value as an SSH string inside the extension's data field, which is the encoding the specification requires ("string <json-array>" inside the data), and strips that inner string again when it parses a certificate. So the certificate's bytes equal the specification's test vectors (see TestWireMatchesTheSpecificationsVectors) and ssh-keygen -s -O "extension:NAME=VALUE" writes the same bytes for the same VALUE.
Every pattern must pass ValidatePattern; duplicates (ignoring case) and more than MaxPatterns patterns are refused.
func Grants ¶
Grants reports whether any of patterns matches host: a domain grant lists the hosts a certificate may be used on, and several patterns are alternatives ("they SHOULD be treated as a logical OR"). An empty list grants nothing.
func MatchDomain ¶
MatchDomain reports whether host is matched by pattern, following the specification of the ssh-domain-grant extension: "The wildcard character `*` matches one or more characters within a single label of a domain name (labels are separated by dots)". So:
- pattern and host have the same number of labels, and each label of the host is matched by the label of the pattern in the same place: a wildcard never crosses a dot;
- '*' stands for one or more characters, never for none, and may appear several times in a label ("prod-*-*");
- letters are compared without regard to ASCII case, as DNS names are (RFC 4343).
A pattern ValidatePattern refuses matches nothing, and neither does a host that is not a valid DNS name in the same sense (which includes a host containing '*'). A trailing dot is not removed from either: give the host as a relative name, "login.example.org".
func ParseDomainGrant ¶
ParseDomainGrant parses an extension value, as found in ssh.Certificate.Permissions.Extensions, into its patterns.
It is strict, and accepts only what EncodeDomainGrant writes:
- a JSON array of strings, in compact form: no whitespace anywhere, nothing before '[' or after ']' -- the specification says the JSON "MUST NOT contain whitespace outside of quoted strings";
- no JSON escapes: a valid pattern never needs one, so an escape can only make two encodings of one grant, or hide a character;
- every pattern valid for ValidatePattern, none repeated (ignoring case), at most MaxPatterns of them, and at most MaxGrantLength bytes in all.
So null, objects, numbers, nested arrays, non-strings, duplicate entries and trailing data are refused. `[]` is accepted and yields an empty, non-nil list, which grants nothing (the specification lets a policy read it either way; this package reads it as no host).
func SetDomainGrant ¶
func SetDomainGrant(cert *ssh.Certificate, patterns []string) error
SetDomainGrant stores the domain grant for patterns in cert, creating its extension map if needed. It is to be called before the certificate is signed.
func ValidatePattern ¶
ValidatePattern reports whether p is a domain pattern this package accepts. The extension's specification requires implementations to "reject malformed domain names" and to "enforce maximum pattern length limits" without saying what they are; this package's rules are:
- at most MaxPatternLength bytes, and labels separated by single dots, each of 1 to MaxLabelLength bytes: no empty label, so no leading, trailing or doubled dot;
- a label is made of ASCII letters, digits, hyphens and the wildcard '*', and does not begin or end with a hyphen (RFC 1123 host names);
- the rightmost label has no wildcard, so no pattern grants every name under a top-level domain, or every single-label name. The specification asks implementations to limit wildcard matching.
Anything else -- other characters, among them '?', '[' and '\' that a shell-style matcher would read as syntax, non-ASCII letters (use the A-label, "xn--..."), underscores, spaces -- is refused.
Types ¶
This section is empty.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
sshcert-authorize
command
Command sshcert-authorize is an AuthorizedPrincipalsCommand for sshd that lets a certificate in only on the hosts its domain grant names, the ssh-domain-grant@core.aai.geant.org extension of the GÉANT/EuroHPC SSH CA profile.
|
Command sshcert-authorize is an AuthorizedPrincipalsCommand for sshd that lets a certificate in only on the hosts its domain grant names, the ssh-domain-grant@core.aai.geant.org extension of the GÉANT/EuroHPC SSH CA profile. |