Expand ~ to HOME in Agent.Socket Add url override for gcloud functions v2 Add logging for parsing the principals go fmt
176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
package sshrimpagent
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"git.narnian.us/lordwelch/sshrimp/internal/config"
|
|
"git.narnian.us/lordwelch/sshrimp/internal/signer"
|
|
"github.com/pkg/browser"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/zitadel/oidc/pkg/oidc"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/crypto/ssh/agent"
|
|
)
|
|
|
|
var Log *logrus.Entry
|
|
|
|
type sshrimpAgent struct {
|
|
oidcClient OidcClient
|
|
signer ssh.Signer
|
|
certificate *ssh.Certificate
|
|
token *oidc.Tokens
|
|
config *config.SSHrimp
|
|
}
|
|
|
|
// NewSSHrimpAgent returns an agent.Agent capable of signing certificates with a SSHrimp Certificate Authority
|
|
func NewSSHrimpAgent(c *config.SSHrimp, signer ssh.Signer) (agent.Agent, error) {
|
|
|
|
oidcClient, err := newOIDCClient(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
go func() {
|
|
|
|
if err = oidcClient.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
|
Log.Logger.Errorf("Server failed: %v", err)
|
|
os.Exit(99)
|
|
}
|
|
}()
|
|
|
|
return &sshrimpAgent{
|
|
oidcClient: oidcClient,
|
|
signer: signer,
|
|
certificate: &ssh.Certificate{},
|
|
token: nil,
|
|
config: c,
|
|
}, nil
|
|
}
|
|
|
|
// authenticate authenticates a oidc token
|
|
func (r *sshrimpAgent) authenticate() error {
|
|
var err error
|
|
if r.token != nil {
|
|
err = oidc.CheckExpiration(r.token.IDTokenClaims, 0)
|
|
} else {
|
|
err = errors.New("no token provided")
|
|
}
|
|
if err != nil {
|
|
Log.Debugln("Token is expired re-authenticating")
|
|
browser.OpenURL("http://" + r.oidcClient.Addr + "/login")
|
|
select {
|
|
case r.token = <-r.oidcClient.OIDCToken:
|
|
return nil
|
|
case <-time.After(30 * time.Second):
|
|
return errors.New("Timeout")
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// RemoveAll clears the current certificate and identity token (including refresh token)
|
|
func (r *sshrimpAgent) RemoveAll() error {
|
|
Log.Debugln("Removing identity token and certificate")
|
|
r.certificate = &ssh.Certificate{}
|
|
r.token = nil
|
|
return nil
|
|
}
|
|
|
|
// Remove has the same functionality as RemoveAll
|
|
func (r *sshrimpAgent) Remove(key ssh.PublicKey) error {
|
|
return r.RemoveAll()
|
|
}
|
|
|
|
// Lock is not supported on this agent
|
|
func (r *sshrimpAgent) Lock(passphrase []byte) error {
|
|
return errors.New("sshrimp-agent: locking not supported")
|
|
}
|
|
|
|
// Unlock is not supported on this agent
|
|
func (r *sshrimpAgent) Unlock(passphrase []byte) error {
|
|
return errors.New("sshrimp-agent: unlocking not supported")
|
|
}
|
|
|
|
// List returns the identities, but also signs the certificate using sshrimp-ca if expired.
|
|
func (r *sshrimpAgent) List() ([]*agent.Key, error) {
|
|
Log.Traceln("Listing current identities")
|
|
validEndDate := time.Unix(int64(r.certificate.ValidBefore), 0)
|
|
|
|
if r.certificate.ValidBefore != uint64(ssh.CertTimeInfinity) && (time.Now().After(validEndDate) || validEndDate.Unix() < 0) {
|
|
Log.Traceln("Certificate has expired")
|
|
Log.Traceln("authenticating token")
|
|
err := r.authenticate()
|
|
|
|
if err != nil {
|
|
Log.Errorf("authenticating the token failed: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
Log.Traceln("signing certificate")
|
|
cert, err := signer.SignCertificateAllRegions(r.signer.PublicKey(), r.token.IDToken, "", r.config)
|
|
if err != nil {
|
|
Log.Errorf("signing certificate failed: %v", err)
|
|
return nil, err
|
|
}
|
|
r.certificate = cert
|
|
}
|
|
|
|
var ids []*agent.Key
|
|
ids = append(ids, &agent.Key{
|
|
Format: r.certificate.Type(),
|
|
Blob: r.certificate.Marshal(),
|
|
Comment: r.certificate.KeyId,
|
|
})
|
|
return ids, nil
|
|
}
|
|
|
|
// Add is not supported on this agent. For multiple OIDC backends, run multiple agents.
|
|
func (r *sshrimpAgent) Add(key agent.AddedKey) error {
|
|
return errors.New("sshrimp-agent: adding identities not supported")
|
|
}
|
|
|
|
// Sign uses our private key to sign the challenge required to authenticate to the ssh host.
|
|
func (r *sshrimpAgent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
|
|
return r.signer.Sign(rand.Reader, data)
|
|
}
|
|
|
|
// Signers list our current signers which there is only one.
|
|
func (r *sshrimpAgent) Signers() ([]ssh.Signer, error) {
|
|
return []ssh.Signer{
|
|
r.signer,
|
|
}, nil
|
|
}
|
|
|
|
func (r *sshrimpAgent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.SignatureFlags) (*ssh.Signature, error) {
|
|
Log.Traceln("requested sign with flags")
|
|
sign, ok := r.signer.(ssh.AlgorithmSigner)
|
|
Log.Tracef("signer is AlgorithmSigner: %v", ok)
|
|
if ok {
|
|
if flags&agent.SignatureFlagRsaSha512 == agent.SignatureFlagRsaSha512 {
|
|
Log.Traceln("sha 512 requested")
|
|
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.KeyAlgoRSASHA512)
|
|
if err == nil {
|
|
Log.Debugln("sha 512 available")
|
|
return s, nil
|
|
}
|
|
}
|
|
if flags&agent.SignatureFlagRsaSha256 == agent.SignatureFlagRsaSha256 {
|
|
Log.Traceln("sha 256 requested")
|
|
s, err := sign.SignWithAlgorithm(rand.Reader, data, ssh.KeyAlgoRSASHA256)
|
|
if err == nil {
|
|
Log.Debugln("sha 256 available")
|
|
return s, nil
|
|
}
|
|
}
|
|
}
|
|
Log.Traceln("signing data")
|
|
return r.Sign(key, data)
|
|
}
|
|
func (r *sshrimpAgent) Extension(extensionType string, contents []byte) ([]byte, error) {
|
|
return nil, agent.ErrExtensionUnsupported
|
|
}
|