geecert

package module
v0.0.0-...-108dae9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 2, 2020 License: Apache-2.0 Imports: 35 Imported by: 2

README

Single Sign On from G-Suite (Google Apps) to SSH

The following provides an end-to-end SSO solution that allows using Google Apps sign-in (where things like 2-factor can be enforced) to allow SSH access to your hosts using the standard SSH command line tools.

Deployment

The server is expected to be built and and run 'as-is', as its configuration is controlled by a configuration file, described below.

For the client we expect your server administrator to build a custom binary that comes pre-baked with your organizations configuration, by copying the sample harness, replacing with your configuration values, and building a binary that you distribute to your users.

Building from source

Both client and server are written in Go. Amongst other things, an advantage of writing this in Go means that it is easy to build and distribute a single static binary with no dependencies which is useful for distributing client login tool within your organization.

First, install go https://golang.org/dl/ and set an appropriate GOPATH in your profile, for example:

cat >> ~/.bash_profile <<EOF
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$PATH
EOF
source ~/.bash_profile

Fetch source and build both the client and the server (the ... is not a typo), can be re-run to fetch updates:

go get -u github.com/continusec/geecert/cmd/...

Verify binaries are built:

ls $GOPATH/bin

Shows:

geecertsample
servegeecerts
Developer notes

If you make any changes to sso.proto, run the following to re-generate new Go code (assumes that protoc is installed):

cd $GOPATH/src/github.com/continusec/geecert
go generate

To build and install new server and client after changing Go source, run:

go install github.com/continusec/geecert/cmd/...

SSO Server

The SSO Server can be built from source assuming a working golang install. It does however compile to a single statically linked binary, so once built that binary can be distributed to another machine without needing anything else.

Running servegeecerts

To run servegeecerts you first need to configure a valid configuration file. The configuration file is in protobuf text format. See sample configuration file here: $GOPATH/src/github.com/continusec/geecert/sample_server_config.proto

See that file for more information on the options available.

Once a config file is prepared, simply run the server:

servegeecerts /path/to/config.proto

If all is good, you should see:

2016/10/18 17:14:29 Serving...

Now, go build and run a client to use.

Host certificates

The CA server has the ability to issue host certificates. If a request is made to: https://your.server/hostCertificate?host=host.name, the CA will check to see if the specified hostname is matched as an allowed host (per the server configuration file), and if so, it will attempt to begin an SSH handshake with that server, and sign the public key that it is presented and return that to the caller.

In this manner this endpoint can be easily called by shell scripts in your fleet to self-sign host certificates.

geecertsample client tool

For the client your server administrator needs to configure and build a custom binary that comes pre-baked with your organizations configuration, by copying the sample harness, replacing with your configuration values, and building a binary that you distribute to your users.

To do so, first make sure the source code is available:

go get -u github.com/continusec/geecert

And, then in your project, e.g. in $GOPATH/src/github.com/you/ssotool/cmd/getmycerts/main.go:

package main

import (
    "flag"
    "log"

    "github.com/continusec/geecert"
)

var LocalConfiguration = geecert.ClientAppConfiguration{
    HostedDomain: "orgname.com",
    ClientID: "xxxxxxx.apps.googleusercontent.com",
    ClientNotSoSecret: "yyyyyyy",
    GRPCPEMCertificate: `-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
`,
    CredentialFileName: ".orgnamesso",
    ShortlivedKeyName:  "id_orgname_shortlived_rsa",
    SectionIdentifier:  "ORGNAME-CA",

    // Other fields are specified via defaults in flags below
}

func main() {
    flag.StringVar(&LocalConfiguration.GRPCServer, "server", "sso.orgname.com:10000", "Address:port of the server to connect to")
    flag.StringVar(&LocalConfiguration.GRPCPEMCertificatePath, "server_cert", "", "Certificate expected from the server for TLS, overrides default in binary")
    flag.BoolVar(&LocalConfiguration.OverrideMachinePolicy, "override_machine_policy", false, "Please don't use this.")
    flag.BoolVar(&LocalConfiguration.OverrideGrpcSecurity, "allow_insecure_connect_to_sso_server", false, "Please don't use this.")
    flag.BoolVar(&LocalConfiguration.UseSystemCaForCert, "server_cert_from_real_ca", false, "Use system CA for server cert.")
    flag.Parse()

    err := geecert.ProcessClient(&LocalConfiguration)
    if err != nil {
        log.Fatal(err)
    }
}

Or similar. Then build it:

go install github.com/you/ssotool/cmd/getmycerts

And run:

getmycerts

(Note, this is intended to be run from an end-client workstation, e.g. your laptop, rather than an intermediate server)

The first time this is run, it will perform an OAuth 2.0 dance with Google to fetch long-lived credentials for your account. If you are running in a nice GUI environment it will launch a browser for you. Otherwise you'll be given a URL to copy/paste.

Then (and on each subsequent run), it will check to see if it has a valid short-lived ID token from Google. If not, it will connect to Google to fetch a new one (which will be granted unless the user (or a domain admin)) revokes access by the SSO tool.

Next it will generate a new key/pair and send the public key and the short lived ID token to the servegeecerts that we ran above. The servegeecerts will validate the ID token, and then, if appropriate, will generate a new certificate for that public key, and send it back the client.

Finally the client will update a set of config files in ~/.ssh. Specifically it will:

  1. Overwrite ~/.ssh/id_orgname_shortlived_rsa with the new private key generated.

  2. Overwrite ~/.ssh/id_orgname_shortlived_rsa.pub with the new public key generated.

  3. Overwrite ~/.ssh/id_orgname_shortlived_rsa-cert.pub with the certicate received for that key.

  4. Edit ~/known_hosts to add (and overwrite this section on subsequent runs) this section:

     # AUTOGENERATED:BEGIN:GEECERT - DO NOT EDIT BETWEEN MARKERS!
     @cert-authority *.yourdomain.com sha-rsa AAAAB3NzaC...qZyhLayRUw== GEECERTCA
     # AUTOGENERATED:END:GEECERT- DO NOT EDIT BETWEEN MARKERS!
    
  5. Edit ~/config to add (and overwrite this section on subsequent runs) this section:

     # AUTOGENERATED:BEGIN:GEECERT - DO NOT EDIT BETWEEN MARKERS!
     Host *.yourdomain.com
         User foo
         IdentityFile ~/.ssh/id_orgname_shortlived_rsa
         IdentitiesOnly yes
         PasswordAuthentication no
     # AUTOGENERATED:END:GEECERT - DO NOT EDIT BETWEEN MARKERS!
    

This instructs the client to use (and only use) the new certificate, and to trust the same CA for host-based authentication. The config returned here is controlled by the server.

Tip: Worried about bad things happening to good config

Consider backing up your ~/.ssh before running the tool if concerned. Alternatively consider running a local git repo until comfortable with what the tool is doing - make it easy to see the differences:

cd ~/.ssh
git init
git add *
git commit -a -m "Initial commit."

Troubleshooting

"FileVault must be enabled" error

The client soft-enforces a minimum security profile that should be present on a workstation on in order to receive credentials to production systems. As such, when present on a Mac, if full disk encryption is not enabled, then the client will not run (and other platforms to follow). The intention is to mitigate against theft of a device that contains credentials.

The best way to fix this error is to enabled FileVault. Alternatively, re-run with --override_machine_policy (if you choose to leave this option in your binary).

Deleting cached credentials

If there are errors coming back from the Google server such as invalid_grant, try removing the saved credentials and re-authorizing the application.

rm ~/.orgnamesso

Then re-run the tool:

getmycerts
Revoking access to Google account

To revoke access to your Google account, visit: https://security.google.com/settings/security/permissions

And remove the application that matches your client ID. Note that since the ID Tokens issued by Google are generally for 1 hour, they will continue to be accepted by the SSO server until they timeout.

Acknowledgements

The author gratefully acknowledges the initial funding for development of this tool by Androgogic Pty Ltd.

Documentation

Index

Constants

View Source
const (
	RedirectOOB       = "urn:ietf:wg:oauth:2.0:oob"
	RedirectLocalhost = "http://localhost"
)

Variables

View Source
var (
	ErrUserDenied       = errors.New("User clicked deny.")
	ErrWrongKeyFileType = errors.New("Wrong key file type.")
	ErrWrongCertType    = errors.New("Wrong cert file type.")
)
View Source
var (
	ErrUnexpectedAlgorithm      = errors.New("ErrUnexpectedAlgorithm")
	ErrMissingKeyID             = errors.New("ErrMissingKeyID")
	ErrMissingCertificate       = errors.New("ErrMissingCertificate")
	ErrUnexpectedServerResponse = errors.New("ErrUnexpectedServerResponse")
	ErrCertificateNotValid      = errors.New("ErrCertificateNotValid")
)
View Source
var (
	ErrInvalidIDToken = errors.New("ErrInvalidIDToken")
)

Functions

func DoBrowserDance

func DoBrowserDance(config *ClientAppConfiguration) (string, string, error)

Try to launch a browser, redirect to local server etc etc Return code, redirect URI, error

func DoOOBDance

func DoOOBDance(config *ClientAppConfiguration) (string, string, error)

func FetchCerts

func FetchCerts(config *ClientAppConfiguration, idToken string, sshDir string, homePathToSSHDir string) error

sshDir is the absolute path homePathToSSHDir is the path to use inside of a config file, this should contain a ~ rather than be absolute as it allows this .ssh dir to be mounted as a volume inside of Docker and work well.

func ProcessClient

func ProcessClient(config *ClientAppConfiguration) error

func Reauthorize

func Reauthorize(config *ClientAppConfiguration, path string) error

Prompt user to

func ReplaceSectionOfFile

func ReplaceSectionOfFile(name string, path string, lines []string, perm os.FileMode, messageIfChanged string) error
Deletes section with name:

# AUTOGENERATED:BEGIN:name ... # AUTOGENERATED:END:name

and adds new section at end with same.

func SafeSave

func SafeSave(path string, contents []byte, perm os.FileMode) error

func SaveCreds

func SaveCreds(path string, creds *CachedCreds) error

func ValidateMachineIsSuitable

func ValidateMachineIsSuitable(config *ClientAppConfiguration) error

We can use this to soft-enforce only giving certificates out if reasonable precautions are in place in the client device, e.g. enforce full disk encryption with machine passcode.

Types

type CachedCreds

type CachedCreds struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	IDToken      string `json:"id_token"`
	RefreshToken string `json:"refresh_token"`
}

func LoadCreds

func LoadCreds(path string) (*CachedCreds, error)

func SwapCodeForTokens

func SwapCodeForTokens(config *ClientAppConfiguration, code, redir string) (*CachedCreds, error)

func SwapRefreshForTokens

func SwapRefreshForTokens(config *ClientAppConfiguration, refreshToken string) (*CachedCreds, error)

type ClientAppConfiguration

type ClientAppConfiguration struct {
	HostedDomain       string // Matches against field in Google response. Should be your domain name.
	ClientID           string // Client ID as configured with Google: https://console.developers.google.com/
	ClientNotSoSecret  string // Client "Secret" corresponding to the Client ID. Note, despite the name, this is not really a secret nor intended to be.
	GRPCPEMCertificate string // If set, Self-signed GRPC server certificate, else GRPCPEMCertificatePath is used
	GRPCServer         string // server:host
	CredentialFileName string // e.g. .geecerttoken

	GRPCPEMCertificatePath string // If set, path to PEM for server certificate

	OverrideMachinePolicy bool // If true, override machine policy such as requiring FDE
	OverrideGrpcSecurity  bool // If true, allow insecure connection to gRPC server
	UseSystemCaForCert    bool // If true, use a system CA instead of self-signed certificate

	DontWriteKeysToDisk  bool // If true, never write private keys/certs to disk, instead use agent only
	OverrideNoKeysToDisk bool // If true, ignore the above, but print a WARNING

	ShortlivedKeyName string // e.g. id_orgname_shortlived_rsa
	SectionIdentifier string // e.g. ORGNAME-CA

	OpenIDConfigurationURL string // e.g.https://accounts.google.com/.well-known/openid-configuration for Google, https://login.microsoftonline.com/<tenancy id>/.well-known/openid-configuration for Azure AD
	OOBURI                 string // if set, overrides std one
	NeverOpenBrowser       bool
	PortForLocalHost       int // if 0, a random one is used

	AudienceInAppID          bool // if set verify "appid" claim for client ID, INSTEAd OF "aud" claim - useful for Azure Access Token
	GetHostedDomainFromEmail bool // if set, check for suffix in email field instead of "hd" cliam. useful for Azure Access Token
	SkipEmailVerified        bool // if set, don't require email_verified field. Useful for Azure Access token

	UseAccessTokenInstead bool // if set, validate access token instead of id token. Useful for Azure AD which won't refresh ID Tokens
	// contains filtered or unexported fields
}

func (*ClientAppConfiguration) ExtractTokenFromCachedCreds

func (c *ClientAppConfiguration) ExtractTokenFromCachedCreds(creds *CachedCreds) string

func (*ClientAppConfiguration) Init

func (config *ClientAppConfiguration) Init()

type IDTokenClaims

type IDTokenClaims struct {
	EmailAddress string
	FirstName    string
	LastName     string
}

func ValidateTokenWithRetryForClock

func ValidateTokenWithRetryForClock(validator IDTokenValidator, idToken string, retries int) (*IDTokenClaims, error)

type IDTokenValidator

type IDTokenValidator interface {
	// Validates a token, including that it matchines the client ID and hosted domain
	// Returns the email address and nil upon success
	ValidateIDToken(idToken string) (*IDTokenClaims, error)
}

type OIDCIDTokenValidator

type OIDCIDTokenValidator struct {
	ConfigurationURL string
	ClientID         string
	HostedDomain     string

	AudienceInAppID          bool // if set verify "appid" claim for client ID, INSTEAd OF "aud" claim - useful for Azure Access Token
	GetHostedDomainFromEmail bool // if set, check for suffix in email field instead of "hd" cliam. useful for Azure Access Token
	SkipEmailVerified        bool // if set, don't require email_verified field. Useful for Azure Access token
	// contains filtered or unexported fields
}

func (*OIDCIDTokenValidator) GetAuthRedirect

func (v *OIDCIDTokenValidator) GetAuthRedirect(redir string) (string, error)

func (*OIDCIDTokenValidator) GetTokenExchangeEndpoint

func (v *OIDCIDTokenValidator) GetTokenExchangeEndpoint() (string, error)

func (*OIDCIDTokenValidator) ValidateIDToken

func (v *OIDCIDTokenValidator) ValidateIDToken(idToken string) (*IDTokenClaims, error)

Validates a token, including that it matchines the client ID and hosted domain Returns the email address and nil upon success

type OOIDClient

type OOIDClient interface {
	IDTokenValidator

	GetAuthRedirect(redir string) (string, error)
	GetTokenExchangeEndpoint() (string, error)
}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL