diff --git a/api/restapi/restapi.go b/api/restapi/restapi.go index 5fbf5340..225ceed0 100644 --- a/api/restapi/restapi.go +++ b/api/restapi/restapi.go @@ -56,6 +56,18 @@ type RESTAPI struct { wg sync.WaitGroup } +// Config provide is used in the NewRESTAPI constructor to define the desired +// parameters for the RESTAPI. The only required field is apiMAddr, the rest +// of the fields are optional. Generally, if an optional field is empty +// the corresponding feature will not be used. +type Config struct { + // required + ApiMAddr ma.Multiaddr + // optional + TLS *tls.Config + BasicAuthCreds map[string]string +} + type route struct { Name string Method string @@ -67,36 +79,23 @@ type peerAddBody struct { PeerMultiaddr string `json:"peer_multiaddress"` } -// NewRESTAPI creates a new REST API component. It receives -// the multiaddress on which the API listens. -func NewRESTAPI(apiMAddr ma.Multiaddr) (*RESTAPI, error) { - n, addr, err := manet.DialArgs(apiMAddr) +// NewRESTAPI creates a new REST API component. It receives the multiaddress on +// which the API listens and a Config object. +func NewRESTAPI(cfg *Config) (*RESTAPI, error) { + n, addr, err := manet.DialArgs(cfg.ApiMAddr) if err != nil { return nil, err } - l, err := net.Listen(n, addr) + var l net.Listener + if cfg.TLS != nil { + l, err = tls.Listen(n, addr, cfg.TLS) + } else { + l, err = net.Listen(n, addr) + } if err != nil { return nil, err } - return newRESTAPI(apiMAddr, l) -} -// NewTlsRESTAPI creates a new REST API component that uses TLS for security -// (authentication, encryption). It receives the multiaddress on which the API -// listens, as well as paths to certificate and private key files -func NewTLSRESTAPI(apiMAddr ma.Multiaddr, tlsCfg *tls.Config) (*RESTAPI, error) { - n, addr, err := manet.DialArgs(apiMAddr) - if err != nil { - return nil, err - } - l, err := tls.Listen(n, addr, tlsCfg) - if err != nil { - return nil, err - } - return newRESTAPI(apiMAddr, l) -} - -func newRESTAPI(apiMAddr ma.Multiaddr, l net.Listener) (*RESTAPI, error) { router := mux.NewRouter().StrictSlash(true) s := &http.Server{ ReadTimeout: RESTAPIServerReadTimeout, @@ -111,23 +110,71 @@ func newRESTAPI(apiMAddr ma.Multiaddr, l net.Listener) (*RESTAPI, error) { api := &RESTAPI{ ctx: ctx, cancel: cancel, - apiAddr: apiMAddr, + apiAddr: cfg.ApiMAddr, listener: l, server: s, rpcReady: make(chan struct{}, 1), } + api.addRoutes(router, cfg.BasicAuthCreds) + api.run() + return api, nil +} + +func (api *RESTAPI) addRoutes(router *mux.Router, basicAuthCreds map[string]string) { for _, route := range api.routes() { + if basicAuthCreds != nil { + route.HandlerFunc = basicAuth(route.HandlerFunc, basicAuthCreds) + } router. Methods(route.Method). Path(route.Pattern). Name(route.Name). Handler(route.HandlerFunc) } - api.router = router - api.run() - return api, nil +} + +func basicAuth(h http.HandlerFunc, credentials map[string]string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + username, password, ok := r.BasicAuth() + if !ok { + resp, err := unauthorizedResp() + if err != nil { + logger.Error(err) + return + } + http.Error(w, resp, 401) + return + } + + authorized := false + for u, p := range credentials { + if u == username && p == password { + authorized = true + } + } + if !authorized { + resp, err := unauthorizedResp() + if err != nil { + logger.Error(err) + return + } + http.Error(w, resp, 401) + return + } + h.ServeHTTP(w, r) + } +} + +func unauthorizedResp() (string, error) { + apiError := api.Error{ + Code: 401, + Message: "Unauthorized", + } + resp, err := json.Marshal(apiError) + return string(resp), err } func (rest *RESTAPI) routes() []route { diff --git a/api/restapi/restapi_test.go b/api/restapi/restapi_test.go index 4586225e..988fdcb6 100644 --- a/api/restapi/restapi_test.go +++ b/api/restapi/restapi_test.go @@ -21,7 +21,7 @@ var ( func testRESTAPI(t *testing.T) *RESTAPI { //logging.SetDebugLogging() apiMAddr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/10002") - rest, err := NewRESTAPI(apiMAddr) + rest, err := NewRESTAPI(&Config{ApiMAddr: apiMAddr}) if err != nil { t.Fatal("should be able to create a new Api: ", err) } diff --git a/config.go b/config.go index 1f1a473f..e3d09fbd 100644 --- a/config.go +++ b/config.go @@ -74,6 +74,12 @@ type Config struct { // SSLCertFile. SSLKeyFile string + // BasicAuthCredentials is a map of username, password pairs used to authorize + // access to the REST API. It is *highly recommended* that you use this in + // conjunction with SSL, as the username/password sent by the client are not + // encrypted when using HTTP without SSL. + BasicAuthCredentials map[string]string + // Listen parameters for the IPFS Proxy. Used by the IPFS // connector component. IPFSProxyAddr ma.Multiaddr @@ -158,6 +164,12 @@ type JSONConfig struct { // SSLCertFile. SSLKeyFile string `json:"ssl_key_file,omitempty"` + // BasicAuthCredentials is a map of username, password pairs used to authorize + // access to the REST API. It is *highly recommended* that you use this in + // conjunction with SSL, as the username/password sent by the client are not + // encrypted when using HTTP without SSL. + BasicAuthCredentials map[string]string `json:"basic_auth_credentials"` + // Listen address for the IPFS Proxy, which forwards requests to // an IPFS daemon. IPFSProxyListenMultiaddress string `json:"ipfs_proxy_listen_multiaddress"` @@ -234,6 +246,7 @@ func (cfg *Config) ToJSONConfig() (j *JSONConfig, err error) { APIListenMultiaddress: cfg.APIAddr.String(), SSLCertFile: cfg.SSLCertFile, SSLKeyFile: cfg.SSLKeyFile, + BasicAuthCredentials: cfg.BasicAuthCredentials, IPFSProxyListenMultiaddress: cfg.IPFSProxyAddr.String(), IPFSNodeMultiaddress: cfg.IPFSNodeAddr.String(), ConsensusDataFolder: cfg.ConsensusDataFolder, @@ -347,6 +360,7 @@ func (jcfg *JSONConfig) ToConfig() (c *Config, err error) { APIAddr: apiAddr, SSLCertFile: jcfg.SSLCertFile, SSLKeyFile: jcfg.SSLKeyFile, + BasicAuthCredentials: jcfg.BasicAuthCredentials, IPFSProxyAddr: ipfsProxyAddr, IPFSNodeAddr: ipfsNodeAddr, ConsensusDataFolder: jcfg.ConsensusDataFolder, diff --git a/ipfs-cluster-ctl/formatters.go b/ipfs-cluster-ctl/formatters.go index d74125be..3636f8de 100644 --- a/ipfs-cluster-ctl/formatters.go +++ b/ipfs-cluster-ctl/formatters.go @@ -56,7 +56,6 @@ func textFormatObject(body []byte, format int) { var obj api.Error textFormatDecodeOn(body, &obj) textFormatPrintError(&obj) - default: var obj interface{} textFormatDecodeOn(body, &obj) diff --git a/ipfs-cluster-ctl/main.go b/ipfs-cluster-ctl/main.go index 7c680e4e..aee5da66 100644 --- a/ipfs-cluster-ctl/main.go +++ b/ipfs-cluster-ctl/main.go @@ -31,6 +31,8 @@ var ( defaultTimeout = 60 defaultProtocol = "http" defaultTransport = http.DefaultTransport + defaultUsername = "" + defaultPassword = "" ) var logger = logging.Logger("cluster-ctl") @@ -89,7 +91,7 @@ func main() { }, cli.BoolFlag{ Name: "no-check-certificate", - Usage: "do not verify server TLS certificate. only valid with `--https` flag.", + Usage: "do not verify server TLS certificate. only valid with --https flag", }, cli.StringFlag{ Name: "encoding, enc", @@ -105,11 +107,30 @@ func main() { Name: "debug, d", Usage: "set debug log level", }, + cli.StringFlag{ + Name: "basic-auth", + Usage: `[:] specify BasicAuth credentials for server that +requires authorization. implies --https, which you can disable with --force-http`, + EnvVar: "CLUSTER_CREDENTIALS", + }, + cli.BoolFlag{ + Name: "force-http, f", + Usage: "force HTTP. only valid when using BasicAuth", + }, } app.Before = func(c *cli.Context) error { defaultHost = c.String("host") defaultTimeout = c.Int("timeout") + // check for BasicAuth credentials + if c.IsSet("basic-auth") { + defaultUsername, defaultPassword = parseCredentials(c.String("basic-auth")) + // turn on HTTPS unless flag says not to + if !c.Bool("force-http") { + err := c.Set("https", "true") + checkErr("setting HTTPS flag for BasicAuth (this should never fail)", err) + } + } if c.Bool("https") { defaultProtocol = "https" defaultTransport = newTLSTransport(c.Bool("no-check-certificate")) @@ -447,9 +468,12 @@ func request(method, path string, body io.Reader, args ...string) *http.Response checkErr("creating request", err) r.WithContext(ctx) + if len(defaultUsername) != 0 { + r.SetBasicAuth(defaultUsername, defaultPassword) + } + client := &http.Client{Transport: defaultTransport} resp, err := client.Do(r) - checkErr(fmt.Sprintf("performing request to %s", defaultHost), err) return resp } @@ -472,6 +496,7 @@ func formatResponse(c *cli.Context, r *http.Response) { case "text": if r.StatusCode > 399 { textFormat(body, formatError) + os.Exit(2) } else { textFormat(body, c.Int("parseAs")) } @@ -484,6 +509,21 @@ func formatResponse(c *cli.Context, r *http.Response) { } } +func parseCredentials(userInput string) (string, string) { + credentials := strings.SplitN(userInput, ":", 2) + switch len(credentials) { + case 1: + // only username passed in (with no trailing `:`), return empty password + return credentials[0], "" + case 2: + return credentials[0], credentials[1] + default: + err := fmt.Errorf("invalid [:] input") + checkErr("parsing credentials", err) + return "", "" + } +} + // JSON output is nice and allows users to build on top. func prettyPrint(buf []byte) { var dst bytes.Buffer diff --git a/ipfs-cluster-service/main.go b/ipfs-cluster-service/main.go index d2a79a5f..b7da9b1c 100644 --- a/ipfs-cluster-service/main.go +++ b/ipfs-cluster-service/main.go @@ -276,13 +276,13 @@ func run(c *cli.Context) error { } var api *restapi.RESTAPI + var tlsCfg *tls.Config if len(cfg.SSLCertFile) != 0 || len(cfg.SSLKeyFile) != 0 { - tlsCfg, err := newTLSConfig(cfg.SSLCertFile, cfg.SSLKeyFile) + tlsCfg, err = newTLSConfig(cfg.SSLCertFile, cfg.SSLKeyFile) checkErr("creating TLS config: ", err) - api, err = restapi.NewTLSRESTAPI(cfg.APIAddr, tlsCfg) - } else { - api, err = restapi.NewRESTAPI(cfg.APIAddr) } + apiConfig := &restapi.Config{ApiMAddr: cfg.APIAddr, TLS: tlsCfg, BasicAuthCreds: cfg.BasicAuthCredentials} + api, err = restapi.NewRESTAPI(apiConfig) checkErr("creating REST API component", err) proxy, err := ipfshttp.NewConnector( diff --git a/ipfscluster_test.go b/ipfscluster_test.go index 710d785f..e8313361 100644 --- a/ipfscluster_test.go +++ b/ipfscluster_test.go @@ -85,7 +85,7 @@ func createComponents(t *testing.T, i int, clusterSecret []byte) (*Config, API, cfg.ReplicationFactor = -1 cfg.MonitoringIntervalSeconds = 2 - api, err := restapi.NewRESTAPI(cfg.APIAddr) + api, err := restapi.NewRESTAPI(&restapi.Config{ApiMAddr: cfg.APIAddr}) checkErr(t, err) ipfs, err := ipfshttp.NewConnector( cfg.IPFSNodeAddr, diff --git a/sharness/config/basic_auth/service.json b/sharness/config/basic_auth/service.json new file mode 100644 index 00000000..56afb657 --- /dev/null +++ b/sharness/config/basic_auth/service.json @@ -0,0 +1,20 @@ +{ + "id": "QmdEtBsfumeH2V6dnx1fgn8zuW7XYjWdgJF4NEYpEBcTsg", + "private_key": "CAASqAkwggSkAgEAAoIBAQC/ZmfWDbwyI0nJdRxgHcTdEaBFQo8sky9E+OOvtwZa5WKoLdHyHOLWxCAdpIHUBbhxz5rkMEWLwPI6ykqLIJToMPO8lJbKVzphOjv4JwpiAPdmeSiYMKLjx5V8MpqU2rwj/Uf3sRL8Gg9/Tei3PZ8cftxN1rkQQeeaOtk0CBxUFZSHEsyut1fbgIeL7TAY+4vCmXW0DBr4wh9fnoES/YivOvSiN9rScgWg6N65LfkI78hzaOJ4Nok2S4vYFCxjTAI9NWFUbhP5eJIFzTU+bZuQZxOn2qsoyw8pNZwuF+JClA/RcgBcCvVZcDH2ueVq/zT++bGCN+EWsAEdvJqJ5bsjAgMBAAECggEAaGDUZ6t94mnUJ4UyQEh7v4OJP7wYkFqEAL0qjfzl/lPyBX1XbQ3Ltwul6AR6uMGV4JszARZCFwDWGLGRDWZrTmTDxyfRQ+9l6vfzFFVWGDQmtz+Dn9uGOWnyX5TJMDxJNec+hBmRHOKpaOd37dYxGz0jr19V9UO7piRJp1J1AHUCypUGv5x1IekioSCu5fEyc7dyWwnmITHBjD08st+bCcjrIUFeXSdJKC8SymYeXdaVE3xH3zVEISKnrfT7bhuKZY1iibZIlXbVLNpyX36LkYJOiCqsMum3u70LH0VvTypkqiDbD4S6qfJ4vvUakpmKpOPutikiP7jkSP+AkaO0AQKBgQDkTuhnDK6+Y0a/HgpHJisji0coO+g2gsIszargHk8nNY2AB8t+EUn7C+Qu8cmrem5V8EXcdxS6z7iAXpJmY1Xepnsz+JP7Q91Lgt3OoqK5EybzUXXKkmNCD65n70Xxn2fEFzm6+GJP3c/HymlDKU2KBCYIyuUeaREjT0Fu3v6tgQKBgQDWnXppJwn4LJHhzFOCeO4zomDJDbLTZCabdKZoFP9r+vtEHAnclDDKx4AYbomSqgERe+DX6HR/tPHRVizP63RYPf7al2mJmPzt1nTkoc1/q5hQoD+oE154dADsW1pUp7AQjwCtys4iq5S0qAwIDpuY8M8bOHwZ+QmBvHYAigJCowKBgQC3HH6TX/2rH463bE2MARXqXSPGJj45sigwrQfW1xhe9zm1LQtN4mn2mvP5nt1D1l82OA6gIzYSGtX8x10eF5/ggqAf78goZ6bOkHh76b8fNzgvQO97eGt5qYAVRjhP8azU/lfEGMEpE1s5/6LrRe41utwSg0C+YkBnlIKDfQDAgQKBgDoBTCF5hK9H1JHzuKpt5uubuo78ndWWnvyrNYKyEirsJddNwLiWcO2NqChyT8qNGkbQdX/Fex89F5KduPTlTYfAEc6g18xxxgK+UM+uj60vArbf6PSTb5gculcnha2VuPdwvx050Cb8uu9s7/uJfzKB+2f/B0O51ID1H+ubYWsDAoGBAKrwGKHyqFTHSPg3XuRA1FgDAoOsfzP9ZJvMEXUWyu/VxjNt+0mRlyGeZ5qb9UZG+K/In4FbC/ux2P/PucCUIbgy/XGPtPXVavMwNbx0MquAcU0FihKXP0CUpi8zwiYc42MF7n/SztQnismxigBMSuJEDurcXXazjfcSRTypduNn", + "cluster_secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641", + "cluster_peers": [], + "bootstrap": [], + "leave_on_shutdown": false, + "cluster_multiaddress": "/ip4/0.0.0.0/tcp/9096", + "api_listen_multiaddress": "/ip4/127.0.0.1/tcp/9094", + "basic_auth_credentials": { + "testuser": "testpass" + }, + "ipfs_proxy_listen_multiaddress": "/ip4/127.0.0.1/tcp/9095", + "ipfs_node_multiaddress": "/ip4/127.0.0.1/tcp/5001", + "state_sync_seconds": 60, + "ipfs_sync_seconds": 130, + "replication_factor": -1, + "monitoring_interval_seconds": 15, + "allocation_strategy": "reposize" +} \ No newline at end of file diff --git a/sharness/ssl/server.crt b/sharness/config/ssl-basic_auth/server.crt similarity index 100% rename from sharness/ssl/server.crt rename to sharness/config/ssl-basic_auth/server.crt diff --git a/sharness/ssl/server.key b/sharness/config/ssl-basic_auth/server.key similarity index 100% rename from sharness/ssl/server.key rename to sharness/config/ssl-basic_auth/server.key diff --git a/sharness/config/ssl-basic_auth/service.json b/sharness/config/ssl-basic_auth/service.json new file mode 100644 index 00000000..61027d9d --- /dev/null +++ b/sharness/config/ssl-basic_auth/service.json @@ -0,0 +1,20 @@ +{ + "id": "QmdEtBsfumeH2V6dnx1fgn8zuW7XYjWdgJF4NEYpEBcTsg", + "private_key": "CAASqAkwggSkAgEAAoIBAQC/ZmfWDbwyI0nJdRxgHcTdEaBFQo8sky9E+OOvtwZa5WKoLdHyHOLWxCAdpIHUBbhxz5rkMEWLwPI6ykqLIJToMPO8lJbKVzphOjv4JwpiAPdmeSiYMKLjx5V8MpqU2rwj/Uf3sRL8Gg9/Tei3PZ8cftxN1rkQQeeaOtk0CBxUFZSHEsyut1fbgIeL7TAY+4vCmXW0DBr4wh9fnoES/YivOvSiN9rScgWg6N65LfkI78hzaOJ4Nok2S4vYFCxjTAI9NWFUbhP5eJIFzTU+bZuQZxOn2qsoyw8pNZwuF+JClA/RcgBcCvVZcDH2ueVq/zT++bGCN+EWsAEdvJqJ5bsjAgMBAAECggEAaGDUZ6t94mnUJ4UyQEh7v4OJP7wYkFqEAL0qjfzl/lPyBX1XbQ3Ltwul6AR6uMGV4JszARZCFwDWGLGRDWZrTmTDxyfRQ+9l6vfzFFVWGDQmtz+Dn9uGOWnyX5TJMDxJNec+hBmRHOKpaOd37dYxGz0jr19V9UO7piRJp1J1AHUCypUGv5x1IekioSCu5fEyc7dyWwnmITHBjD08st+bCcjrIUFeXSdJKC8SymYeXdaVE3xH3zVEISKnrfT7bhuKZY1iibZIlXbVLNpyX36LkYJOiCqsMum3u70LH0VvTypkqiDbD4S6qfJ4vvUakpmKpOPutikiP7jkSP+AkaO0AQKBgQDkTuhnDK6+Y0a/HgpHJisji0coO+g2gsIszargHk8nNY2AB8t+EUn7C+Qu8cmrem5V8EXcdxS6z7iAXpJmY1Xepnsz+JP7Q91Lgt3OoqK5EybzUXXKkmNCD65n70Xxn2fEFzm6+GJP3c/HymlDKU2KBCYIyuUeaREjT0Fu3v6tgQKBgQDWnXppJwn4LJHhzFOCeO4zomDJDbLTZCabdKZoFP9r+vtEHAnclDDKx4AYbomSqgERe+DX6HR/tPHRVizP63RYPf7al2mJmPzt1nTkoc1/q5hQoD+oE154dADsW1pUp7AQjwCtys4iq5S0qAwIDpuY8M8bOHwZ+QmBvHYAigJCowKBgQC3HH6TX/2rH463bE2MARXqXSPGJj45sigwrQfW1xhe9zm1LQtN4mn2mvP5nt1D1l82OA6gIzYSGtX8x10eF5/ggqAf78goZ6bOkHh76b8fNzgvQO97eGt5qYAVRjhP8azU/lfEGMEpE1s5/6LrRe41utwSg0C+YkBnlIKDfQDAgQKBgDoBTCF5hK9H1JHzuKpt5uubuo78ndWWnvyrNYKyEirsJddNwLiWcO2NqChyT8qNGkbQdX/Fex89F5KduPTlTYfAEc6g18xxxgK+UM+uj60vArbf6PSTb5gculcnha2VuPdwvx050Cb8uu9s7/uJfzKB+2f/B0O51ID1H+ubYWsDAoGBAKrwGKHyqFTHSPg3XuRA1FgDAoOsfzP9ZJvMEXUWyu/VxjNt+0mRlyGeZ5qb9UZG+K/In4FbC/ux2P/PucCUIbgy/XGPtPXVavMwNbx0MquAcU0FihKXP0CUpi8zwiYc42MF7n/SztQnismxigBMSuJEDurcXXazjfcSRTypduNn", + "cluster_secret": "84399cd0be811c2ca372d6ca473ffd73c09034f991c5e306fe9ada6c5fcfb641", + "cluster_peers": [], + "bootstrap": [], + "leave_on_shutdown": false, + "cluster_multiaddress": "/ip4/0.0.0.0/tcp/9096", + "api_listen_multiaddress": "/ip4/127.0.0.1/tcp/9094", + "ssl_cert_file": "test-config/server.crt", + "ssl_key_file": "test-config/server.key", + "basic_auth_credentials": { "testuser" : "testpass" }, + "ipfs_proxy_listen_multiaddress": "/ip4/127.0.0.1/tcp/9095", + "ipfs_node_multiaddress": "/ip4/127.0.0.1/tcp/5001", + "state_sync_seconds": 60, + "ipfs_sync_seconds": 130, + "replication_factor": -1, + "monitoring_interval_seconds": 15, + "allocation_strategy": "reposize" +} diff --git a/sharness/config/ssl/server.crt b/sharness/config/ssl/server.crt new file mode 100644 index 00000000..b4f82ce0 --- /dev/null +++ b/sharness/config/ssl/server.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID7TCCAtWgAwIBAgIJAMqpHdKRMzMLMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8xDzANBgNVBAcMBmdvbGRlbjEMMAoG +A1UECgwDQ1NNMREwDwYDVQQLDAhTZWN0b3IgNzEMMAoGA1UEAwwDQm9iMSAwHgYJ +KoZIhvcNAQkBFhFtaW5pc3RlckBtb3N3Lm9yZzAeFw0xNzA3MjExNjA5NTlaFw0y +NzA3MTkxNjA5NTlaMIGCMQswCQYDVQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8x +DzANBgNVBAcMBmdvbGRlbjEMMAoGA1UECgwDQ1NNMREwDwYDVQQLDAhTZWN0b3Ig +NzEMMAoGA1UEAwwDQm9iMSAwHgYJKoZIhvcNAQkBFhFtaW5pc3RlckBtb3N3Lm9y +ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALuoP8PehGItmKPi3+8S +IV1qz8C3FiK85X/INxYLjyuzvpmDROtlkOvdmPCJrveKDZF7ECQpwIGApFbnKCCW +3zdOPQmAVzm4N8bvnzFtM9mTm8qKb9SwRi6ZLZ/qXo98t8C7CV6FaNKUkIw0lUes +ZiXEcmknrlPy3svaDQVoSOH8L38d0g4geqiNrMmZDaGe8FAYdpCoeYDIm/u0Ag9y +G3+XAbETxWhkfTyH3XcQ/Izg0wG9zFY8y/fyYwC+C7+xF75x4gbIzHAY2iFS2ua7 +GTKa2GZhOXtMuzJ6cf+TZW460Z+O+PkA1aH01WrGL7iCW/6Cn9gPRKL+IP6iyDnh +9HMCAwEAAaNkMGIwDwYDVR0RBAgwBocEfwAAATAdBgNVHQ4EFgQU9mXv8mv/LlAa +jwr8X9hzk52cBagwHwYDVR0jBBgwFoAU9mXv8mv/LlAajwr8X9hzk52cBagwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAIxqpKYzF6A9RlLso0lkF +nYfcyeVAvi03IBdiTNnpOe6ROa4gNwKH/JUJMCRDPzm/x78+srCmrcCCAJJTcqgi +b84vq3DegGPg2NXbn9qVUA1SdiXFelqMFwLitDn2KKizihEN4L5PEArHuDaNvLI+ +kMr+yZSALWTdtfydj211c7hTBvFqO8l5MYDXCmfoS9sqniorlNHIaBim/SNfDsi6 +8hAhvfRvk3e6dPjAPrIZYdQR5ROGewtD4F/anXgKY2BmBtWwd6gbGeMnnVi1SGRP +0UHc4O9aq9HrAOFL/72WVk/kyyPyJ/GtSaPYL1OFS12R/l0hNi+pER7xDtLOVHO2 +iw== +-----END CERTIFICATE----- diff --git a/sharness/config/ssl/server.key b/sharness/config/ssl/server.key new file mode 100644 index 00000000..28da7be2 --- /dev/null +++ b/sharness/config/ssl/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAu6g/w96EYi2Yo+Lf7xIhXWrPwLcWIrzlf8g3FguPK7O+mYNE +62WQ692Y8Imu94oNkXsQJCnAgYCkVucoIJbfN049CYBXObg3xu+fMW0z2ZObyopv +1LBGLpktn+pej3y3wLsJXoVo0pSQjDSVR6xmJcRyaSeuU/Ley9oNBWhI4fwvfx3S +DiB6qI2syZkNoZ7wUBh2kKh5gMib+7QCD3Ibf5cBsRPFaGR9PIfddxD8jODTAb3M +VjzL9/JjAL4Lv7EXvnHiBsjMcBjaIVLa5rsZMprYZmE5e0y7Mnpx/5NlbjrRn474 ++QDVofTVasYvuIJb/oKf2A9Eov4g/qLIOeH0cwIDAQABAoIBAAOYreArG45mIU7C +wlfqmQkZSvH+kEYKKLvSMnwRrKTBxR1cDq4UPDrI/G1ftiK4Wpo3KZAH3NCejoe7 +1mEJgy2kKjdMZl+M0ETXws1Hsn6w/YNcM9h3qGCsPtuZukY1ta/T5dIR7HhcsIh/ +WX0OKMcAhNDPGeAx/2MYwrcf0IXELx0+eP1fuBllkajH14J8+ZkVrBMDhqppn8Iq +f9poVNQliJtN7VkL6lJ60HwoVNGEhFaOYphn3CR/sCc6xl+/CzV4h6c5X/RIUfDs +kjgl9mlPFuWq9S19Z+XVfLSE+sYd6LDrh0IZEx9s0OfOjucH2bUAuKNDnCq0wW70 +FzH6KoECgYEA4ZOcAMgujk8goL8nleNjuEq7d8pThAsuAy5vq9oyol8oe+p1pXHR +SHP6wHyhXeTS5g1Ej+QV6f0v9gVFS2pFqTXymc9Gxald3trcnheodZXx63YbxHm2 +H7mYWyZvq05A0qRLmmqCoSRJHUOkH2wVqgj9KsVYP1anIhdykbycansCgYEA1Pdp +uAfWt/GLZ7B0q3JPlVvusf97wBIUcoaxLHGKopvfsaFp0EY3NRxLSTaZ0NPOxTHh +W6xaIlBmKllyt6q8W609A8hrXayV1yYnVE44b5UEMhVlfRFeEdf9Sp4YdQJ8r1J0 +QA89jHCjf8VocP5pSJz5tXvWHhmaotXBthFgWGkCgYEAiy7dwenCOBKAqk5n6Wb9 +X3fVBguzzjRrtpDPXHTsax1VyGeZIXUB0bemD2CW3G1U55dmJ3ZvQwnyrtT/tZGj +280qnFa1bz6aaegW2gD082CKfWNJrMgAZMDKTeuAWW2WN6Ih9+wiH7VY25Kh0LWL +BHg5ZUuQsLwRscpP6bY7uMMCgYEAwY23hK2DJZyfEXcbIjL7R4jNMPM82nzUHp5x +6i2rTUyTitJj5Anc5SU4+2pnc5b9RtWltva22Jbvs6+mBm1jUYLqgESn5/QSHv8r +IYER47+wl4BAw+GD+H2wVB/JpJbFEWbEBvCTBM/emSKmYIOo1njsrlfFa4fjtfjG +XJ4ATXkCgYEAzeSrCCVrfPMLCmOijIYD1F7TMFthosW2JJie3bcHZMu2QEM8EIif +YzkUvMaDAXJ4VniTHkDf3ubRoUi3DwLbvJIPnoOlx3jmzz6KYiEd+uXx40Yrebb0 +V9GB2S2q1RY7wsFoCqT/mq8usQkjr3ulYMJqeIWnCTWgajXWqAHH/Mw= +-----END RSA PRIVATE KEY----- diff --git a/sharness/ssl/service.json b/sharness/config/ssl/service.json similarity index 100% rename from sharness/ssl/service.json rename to sharness/config/ssl/service.json diff --git a/sharness/lib/test-lib.sh b/sharness/lib/test-lib.sh index 0c4b325a..837708b3 100755 --- a/sharness/lib/test-lib.sh +++ b/sharness/lib/test-lib.sh @@ -96,6 +96,6 @@ test_clean_ipfs(){ test_clean_cluster(){ kill -1 "$CLUSTER_D_PID" - rm -rf test-config + rm -rf 'test-config' sleep 2 } diff --git a/sharness/t0040-ssl-simple-exchange.sh b/sharness/t0040-ssl-simple-exchange.sh index cf685f42..fe417af7 100755 --- a/sharness/t0040-ssl-simple-exchange.sh +++ b/sharness/t0040-ssl-simple-exchange.sh @@ -2,7 +2,7 @@ test_description="Test service + ctl SSL interaction" -ssl_config="`pwd`/ssl" +ssl_config="`pwd`/config/ssl" . lib/test-lib.sh diff --git a/sharness/t0041-ssl-enforcement.sh b/sharness/t0041-ssl-enforcement.sh index 16c3c95a..9d2a0a59 100755 --- a/sharness/t0041-ssl-enforcement.sh +++ b/sharness/t0041-ssl-enforcement.sh @@ -14,7 +14,6 @@ test_expect_success "prerequisites" ' ' test_expect_success "ssl enforced by client" ' - test_cluster_config id=`cluster_id` test_must_fail ipfs-cluster-ctl --https --no-check-certificate id ' diff --git a/sharness/t0042-basic-auth.sh b/sharness/t0042-basic-auth.sh new file mode 100755 index 00000000..7f78a8be --- /dev/null +++ b/sharness/t0042-basic-auth.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description="Test service + ctl SSL interaction" + +config="`pwd`/config/basic_auth" + +. lib/test-lib.sh + +test_ipfs_init +cleanup test_clean_ipfs +test_cluster_init "$config" +cleanup test_clean_cluster + +test_expect_success "prerequisites" ' + test_have_prereq IPFS && test_have_prereq CLUSTER +' + +test_expect_success "BasicAuth fails without credentials" ' + id=`cluster_id` + { test_must_fail ipfs-cluster-ctl id; } | grep -A1 "401" | grep "Unauthorized" +' + +test_expect_success "BasicAuth fails with bad credentials" ' + id=`cluster_id` + { test_must_fail ipfs-cluster-ctl --basic-auth "testuser:badpass" --force-http id; } | grep -A1 "401" | grep "Unauthorized" + { test_must_fail ipfs-cluster-ctl --basic-auth "baduser:testpass" --force-http id; } | grep -A1 "401" | grep "Unauthorized" + { test_must_fail ipfs-cluster-ctl --basic-auth "baduser:badpass" --force-http id; } | grep -A1 "401" | grep "Unauthorized" +' + +test_expect_success "BasicAuth over HTTP succeeds with CLI flag credentials" ' + id=`cluster_id` + ipfs-cluster-ctl --basic-auth "testuser:testpass" --force-http id | grep -q "$id" +' + +test_expect_success "BasicAuth succeeds with env var credentials" ' + id=`cluster_id` + export CLUSTER_CREDENTIALS="testuser:testpass" + ipfs-cluster-ctl --force-http id | egrep -q "$id" +' + +test_done diff --git a/sharness/t0043-ssl-basic-auth.sh b/sharness/t0043-ssl-basic-auth.sh new file mode 100755 index 00000000..d89985c7 --- /dev/null +++ b/sharness/t0043-ssl-basic-auth.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description="Test service + ctl SSL interaction" + +config="`pwd`/config/ssl-basic_auth" + +. lib/test-lib.sh + +test_ipfs_init +cleanup test_clean_ipfs +test_cluster_init "$config" +cleanup test_clean_cluster + +test_expect_success "prerequisites" ' + test_have_prereq IPFS && test_have_prereq CLUSTER +' + +test_expect_success "ssl interaction succeeds" ' + id=`cluster_id` + ipfs-cluster-ctl --no-check-certificate --basic-auth "testuser:testpass" id | egrep -q "$id" +' + +test_done