diff --git a/README.md b/README.md index 6ad0c2bab9..0704fe3da6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Prerequisites: - [docker and docker-compose](http://docs.docker.com/compose/install/) - [Notary server configuration](#configure-notary-server) -As setup, lets build notary and then start up a local notary-server. +As setup, lets build notary and then start up a local notary-server (don't forget to add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine, add `$(docker-machine ip) notary-server`). ```sh make binaries @@ -50,7 +50,8 @@ docker-compose build docker-compose up -d ``` -Note: To use the local notary server append `-s http://localhost:4443` to all of the commands below. +Note: In order to have notary use the local notary server and development root CA we can load the local development configuration by appending `-c cmd/notary/config.json` to every command. If you would rather not have to use `-c` on every command, copy `cmd/notary/config.json and cmd/notary/root-ca.crt` to `~/.notary`. + First, lets initiate a notary collection called `example.com/scripts` @@ -95,7 +96,7 @@ expand this to other storage options. ## Setup for Development The notary repository comes with Dockerfiles and a docker-compose file -to faciliate development. Simply run the following commands to start +to facilitate development. Simply run the following commands to start a notary server with a temporary MySQL database in containers: ``` @@ -110,6 +111,16 @@ by running `boot2docker ip`, with kitematic, `echo $DOCKER_HOST` should show the IP of the VM). If you are using the default Linux setup, you need to add `127.0.0.1 notary` to your hosts file. +## Successfully connecting over TLS + +By default notary-server runs with TLS with certificates signed by a local +CA. In order to be able to successfully connect to it using +either `curl` or `openssl`, you will have to use the root CA file in `fixtures/root-ca.crt`. + +OpenSSL example: + +`openssl s_client -connect notary-server:4443 -CAfile fixtures/root-ca.crt` + ## Compiling Notary Server Prerequisites: @@ -166,9 +177,17 @@ either via the CA of your choice, or a self signed certificate. If using the pem and key provided in fixtures, either: - add `fixtures/root-ca.crt` to your trusted root certificates + - use the default configuration for notary client that loads the CA root for you by using the flag `-c ./cmd/notary/config.json` - disable TLS verification by adding the following option notary configuration file in `~/.notary/config.json`: "skipTLSVerify": true Otherwise, you will see TLS errors or X509 errors upon initializing the -notary collection. +notary collection: + +``` +$ notary list diogomonica.com/openvpn +* fatal: Get https://notary-server:4443/v2/: x509: certificate signed by unknown authority +$ notary list diogomonica.com/openvpn -c cmd/notary/config.json +latest b1df2ad7cbc19f06f08b69b4bcd817649b509f3e5420cdd2245a85144288e26d 4056 +``` diff --git a/cmd/notary-server/config.json b/cmd/notary-server/config.json index 346126cb5c..24c72c4817 100644 --- a/cmd/notary-server/config.json +++ b/cmd/notary-server/config.json @@ -1,6 +1,8 @@ { "server": { - "addr": ":4443" + "addr": ":4443", + "tls_key_file": "./fixtures/notary-server.key", + "tls_cert_file": "./fixtures/notary-server.crt" }, "trust_service": { "type": "remote", diff --git a/cmd/notary-server/dev-config.json b/cmd/notary-server/dev-config.json deleted file mode 100644 index a70ced6b18..0000000000 --- a/cmd/notary-server/dev-config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "server": { - "addr": ":4443", - "tls_key_file": "./fixtures/notary-server.key", - "tls_cert_file": "./fixtures/notary-server.crt" - }, - "trust_service": { - "type": "local", - "hostname": "", - "port": "", - "key_algorithm": "ed25519" - }, - "logging": { - "level": "debug" - }, - "storage": { - "backend": "mysql", - "db_url": "root@tcp(localhost:3306)/notary" - } -} diff --git a/cmd/notary/config.json b/cmd/notary/config.json new file mode 100644 index 0000000000..6b9f1bf0cf --- /dev/null +++ b/cmd/notary/config.json @@ -0,0 +1,7 @@ +{ + "remote_server": { + "url": "https://notary-server:4443", + "root_ca": "root-ca.crt" + } +} + diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 2f59fc3c36..a19440d7c4 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -7,24 +7,30 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/docker/notary/pkg/passphrase" "github.com/docker/notary/version" + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) -const configFileName string = "config" -const defaultTrustDir string = ".notary/" -const defaultServerURL = "https://notary-server:4443" -const idSize = 64 +const ( + configDir = ".notary/" + defaultServerURL = "https://notary-server:4443" + idSize = 64 +) -var rawOutput bool -var trustDir string -var remoteTrustServer string -var verbose bool -var retriever passphrase.Retriever +var ( + rawOutput bool + verbose bool + trustDir string + configFile string + remoteTrustServer string + configPath string + configFileName = "config" + configFileExt = "json" + retriever passphrase.Retriever +) func init() { retriever = getPassphraseRetriever() @@ -45,17 +51,27 @@ func parseConfig() { if homeDir == "" { fatalf("cannot get current user home directory") } - trustDir = filepath.Join(homeDir, filepath.Dir(defaultTrustDir)) + trustDir = filepath.Join(homeDir, filepath.Dir(configDir)) logrus.Debugf("no trust directory provided, using default: %s", trustDir) } else { logrus.Debugf("trust directory provided: %s", trustDir) } - // Setup the configuration details + // If there was a commandline configFile set, we parse that. + // If there wasn't we attempt to find it on the default location ~/.notary/config + if configFile != "" { + configFileExt = strings.TrimPrefix(filepath.Ext(configFile), ".") + configFileName = strings.TrimSuffix(filepath.Base(configFile), filepath.Ext(configFile)) + configPath = filepath.Dir(configFile) + } else { + configPath = trustDir + } + + // Setup the configuration details into viper viper.SetConfigName(configFileName) - viper.AddConfigPath(trustDir) - viper.SetConfigType("json") + viper.SetConfigType(configFileExt) + viper.AddConfigPath(configPath) // Find and read the config file err := viper.ReadInConfig() @@ -69,11 +85,6 @@ func parseConfig() { } func main() { - serverURL := os.Getenv("NOTARY_SERVER_URL") - if serverURL == "" { - serverURL = defaultServerURL - } - var notaryCmd = &cobra.Command{ Use: "notary", Short: "notary allows the creation of trusted collections.", @@ -92,25 +103,26 @@ func main() { notaryCmd.AddCommand(versionCmd) notaryCmd.PersistentFlags().StringVarP(&trustDir, "trustdir", "d", "", "directory where the trust data is persisted to") + notaryCmd.PersistentFlags().StringVarP(&configFile, "configFile", "c", "", "path to the configuration file to use") notaryCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") notaryCmd.AddCommand(cmdKey) notaryCmd.AddCommand(cmdCert) notaryCmd.AddCommand(cmdTufInit) - cmdTufInit.Flags().StringVarP(&remoteTrustServer, "server", "s", serverURL, "Remote trust server location") + cmdTufInit.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.AddCommand(cmdTufList) cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a nonpretty printed version of the targets list. Useful if you need to parse the list.") - cmdTufList.Flags().StringVarP(&remoteTrustServer, "server", "s", serverURL, "Remote trust server location") + cmdTufList.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.AddCommand(cmdTufAdd) notaryCmd.AddCommand(cmdTufRemove) notaryCmd.AddCommand(cmdTufStatus) notaryCmd.AddCommand(cmdTufPublish) - cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "server", "s", serverURL, "Remote trust server location") + cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.AddCommand(cmdTufLookup) cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a nonpretty printed version of the targets list. Useful if you need to parse the list.") - cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "server", "s", serverURL, "Remote trust server location") + cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.AddCommand(cmdVerify) - cmdVerify.Flags().StringVarP(&remoteTrustServer, "server", "s", serverURL, "Remote trust server location") + cmdVerify.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location") notaryCmd.Execute() } diff --git a/cmd/notary/root-ca.crt b/cmd/notary/root-ca.crt new file mode 100644 index 0000000000..ca860abfd4 --- /dev/null +++ b/cmd/notary/root-ca.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFhjCCA26gAwIBAgIJAMJ4Mtt6YhNLMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G +A1UECgwGRG9ja2VyMRowGAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTAeFw0xNTA3 +MTYwNDI1MDBaFw0yNTA3MTMwNDI1MDBaMF8xCzAJBgNVBAYTAlVTMQswCQYDVQQI +DAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGRG9ja2VyMRow +GAYDVQQDDBFOb3RhcnkgVGVzdGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMzUzq2O07tm3A/4emCN/294jUBnNeGlM4TgsB8W9ingw9CU7oBn +CRTK94cGDHTb5ofcj9Kt4/dSL52uJpkZshmAga4fDDhtntnUHaKYzjoZSKZtq7qV +hC1Dah7s3zftZn4NHiRe82loXH/W0//0MWdQCaLc8E0rd/amrd6EO+5SUwF4dXSk +nWoo3oxtOEnb1uQcWWIiwLRmd1pw3PW/bt/SHssD5dJ+78/nR1qCHhJyLVpylMiy +WijkMKW7mbQFefuCOsQ0QvGG3BrTLu+fVs9GYNzHC+L1bSQbfts4nOSodcB/klhd +mbgVW8mrgeHww/jgb2WJW9Y3RFNp/VEuhVrHiz/NW2qE3nPLEnu0vd50jYIXbvBm +fbhCoJntYAiCY0l8v+POgP3ACtsS41rcn8VyD3Ho4u4186ki71+QRQTsUk2MXRV6 +AKQ9u4Cl4d0tV1oHjVyiKDv8PNakNrI48KmnF9R9wMgzDHIoBVQZraVTyPwW9HvS +8K3Lsm6QAE7pErideOyBViOiiqvW7rUaLERTkhGirX2RChwhYLtYIj0LitgzdaT4 +JD1JxonqN30g2jk1+mJKMEeWBMTjFqtzuQPYH3HkHKxoNfvEuL5fsZSmhV/mR+yW +lSe1f8r1qpAACj/K3mome/z8UhNxzEW8TCYkwamLkAPF485W64KIYI1tAgMBAAGj +RTBDMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgFGMB0GA1UdDgQW +BBR1DNVNxOFsi9Z7xXfnT2PH+DtoWTANBgkqhkiG9w0BAQsFAAOCAgEAUbbrI3OQ +5XO8HHpoTwVqFzSzKOuSSrcMGrv67rn+2HvVJYfxtusZBS6+Rw7QVG3daPS+pSNX +NM1qyin3BjpNR2lI771yyK/yjjNH9pZPR+8ThJ8/77roLJudTCCPt49PoYgSQQsp +IB75PlqnTWVwccW9pm2zSdqDxFeZpTpwEvgyX8MNCfYeynxp5+S81593z8iav16u +t2I38NyFJKuxin9zNkxkpf/a9Pr/Gk56gw1OfHXp+sW/6KIzx8fjQuL6P8HEpwVG +zXXA8fMX91cIFI4+DTc8mPjtYvT6/PzDWE/q6FZZnbHJ50Ngg5D8uFN5lLgZFNtf +ITeoNjTk2koq8vvTW8FDpMkb50zqGdBoIdDtRFd3oot+MEg+6mba+Kttwg05aJ9a +SIIxjvU4NH6qOXBSgzaI1hMr7DTBnaXxMEBiaNaPg2nqi6uhaUOcVw3F01yBfGfX +aGsNLKpFiKFYQfOR1M2ho/7AL19GYQD3IFWDJqk0/eQLfFR74iKVMz6ndwt9F7A8 +0xxGXGpw2NJQTWLQui4Wzt33q541ihzL7EDtybBScUdIOIEO20mHr2czFoTL9IKx +rU0Ck5BMyMBB+DOppP+TeKjutAI1yRVsNoabOuK4oo/FmqysgQoHEE+gVUThrrpE +wV1EBILkX6O4GiMqu1+x92/yCmlKEg0Q6MM= +-----END CERTIFICATE----- + diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 5273e614cd..6e61d77e81 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -4,12 +4,14 @@ import ( "bufio" "crypto/sha256" "crypto/tls" + "crypto/x509" "fmt" "io/ioutil" "net" "net/http" "net/url" "os" + "path/filepath" "strings" "time" @@ -20,6 +22,7 @@ import ( "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/pkg/term" notaryclient "github.com/docker/notary/client" + "github.com/docker/notary/trustmanager" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -93,7 +96,7 @@ func tufAdd(cmd *cobra.Command, args []string) { parseConfig() // no online operations are performed by add so the transport argument // should be nil - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, nil, retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), nil, retriever) if err != nil { fatalf(err.Error()) } @@ -118,7 +121,7 @@ func tufInit(cmd *cobra.Command, args []string) { gun := args[0] parseConfig() - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, getTransport(gun, false), retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, false), retriever) if err != nil { fatalf(err.Error()) } @@ -160,7 +163,7 @@ func tufList(cmd *cobra.Command, args []string) { gun := args[0] parseConfig() - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, getTransport(gun, true), retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, true), retriever) if err != nil { fatalf(err.Error()) } @@ -186,7 +189,7 @@ func tufLookup(cmd *cobra.Command, args []string) { targetName := args[1] parseConfig() - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, getTransport(gun, true), retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, true), retriever) if err != nil { fatalf(err.Error()) } @@ -208,7 +211,7 @@ func tufStatus(cmd *cobra.Command, args []string) { gun := args[0] parseConfig() - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, nil, retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), nil, retriever) if err != nil { fatalf(err.Error()) } @@ -242,7 +245,7 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, getTransport(gun, false), retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, false), retriever) if err != nil { fatalf(err.Error()) } @@ -264,7 +267,7 @@ func tufRemove(cmd *cobra.Command, args []string) { // no online operation are performed by remove so the transport argument // should be nil. - repo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, nil, retriever) + repo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), nil, retriever) if err != nil { fatalf(err.Error()) } @@ -291,7 +294,7 @@ func verify(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] - nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, remoteTrustServer, getTransport(gun, true), retriever) + nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, true), retriever) if err != nil { fatalf(err.Error()) } @@ -357,11 +360,28 @@ func (ps passwordStore) Basic(u *url.URL) (string, string) { } func getTransport(gun string, readOnly bool) http.RoundTripper { + // Attempt to get a root CA from the config file. Nil is the host defaults. + rootPool := x509.NewCertPool() + rootCAFile := viper.GetString("remote_server.root_ca") + if rootCAFile != "" { + // If we haven't been given an Absolute path, we assume it's relative + // from the configuration directory (~/.notary by default) + if !filepath.IsAbs(rootCAFile) { + rootCAFile = filepath.Join(configPath, rootCAFile) + } + rootCert, err := trustmanager.LoadCertFromFile(rootCAFile) + if err != nil { + fatalf("could not load root ca file. %s", err.Error()) + } + rootPool.AddCert(rootCert) + } + // skipTLSVerify is false by default so verification will // be performed. tlsConfig := &tls.Config{ - InsecureSkipVerify: viper.GetBool("skipTLSVerify"), + InsecureSkipVerify: viper.GetBool("remote_server.skipTLSVerify"), MinVersion: tls.VersionTLS10, + RootCAs: rootPool, } base := &http.Transport{ @@ -386,9 +406,13 @@ func tokenAuth(baseTransport *http.Transport, gun string, readOnly bool) http.Ro Transport: authTransport, Timeout: 5 * time.Second, } - endpoint, err := url.Parse(remoteTrustServer) + trustServerURL := getRemoteTrustServer() + endpoint, err := url.Parse(trustServerURL) if err != nil { - fatalf("could not parse remote trust server url (%s): %s", remoteTrustServer, err.Error()) + fatalf("could not parse remote trust server url (%s): %s", trustServerURL, err.Error()) + } + if endpoint.Scheme == "" { + fatalf("trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL) } subPath, err := url.Parse("v2/") if err != nil { @@ -416,3 +440,15 @@ func tokenAuth(baseTransport *http.Transport, gun string, readOnly bool) http.Ro modifier := transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) return transport.NewTransport(baseTransport, modifier) } + +func getRemoteTrustServer() string { + if remoteTrustServer == "" { + configRemote := viper.GetString("remote_server.url") + if configRemote != "" { + remoteTrustServer = configRemote + } else { + remoteTrustServer = defaultServerURL + } + } + return remoteTrustServer +} diff --git a/notary-signer-Dockerfile b/notary-signer-Dockerfile index ed4a837570..c000dff67c 100644 --- a/notary-signer-Dockerfile +++ b/notary-signer-Dockerfile @@ -1,4 +1,4 @@ -FROM diogomonica/golang-softhsm2 +FROM dockersecurity/golang-softhsm2 MAINTAINER Diogo Monica "diogo@docker.com" # CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token diff --git a/signer/Dockerfile b/signer/Dockerfile new file mode 100644 index 0000000000..9628b745f3 --- /dev/null +++ b/signer/Dockerfile @@ -0,0 +1,36 @@ +FROM golang:1.5.1 + +MAINTAINER Diogo Monica "diogo@docker.com" + +RUN apt-get update && apt-get install -y \ + sqlite3 \ + && rm -rf /var/lib/apt/lists/* + +RUN buildDeps=' \ + autoconf \ + automake \ + build-essential \ + libtool \ + libssl-dev \ + libsqlite3-dev \ + ' \ + && set -x \ + && apt-get update && apt-get install -y $buildDeps --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* \ + && git clone https://github.com/opendnssec/SoftHSMv2.git /usr/src/SoftHSMv2 \ + && cd /usr/src/SoftHSMv2 \ + && sh autogen.sh \ + && ./configure --with-objectstore-backend-db \ + && make \ + && make install \ + && rm -rf /usr/src/SoftHSMv2 \ + && apt-get purge -y --auto-remove $buildDeps \ + && mkdir -p /softhsm2/tokens + +# Default locations for the SoftHSM2 configuration and PKCS11 bindings +ENV SOFTHSM2_CONF="/etc/softhsm2/softhsm2.conf" +ENV LIBDIR="/usr/local/lib/softhsm/" +COPY ./softhsm2.conf /etc/softhsm2/softhsm2.conf + +ENTRYPOINT ["softhsm2-util"] +