Merge pull request #147 from ipfs/feat/api-basic-auth
Issue #121: BasicAuth REST API
This commit is contained in:
commit
6e406109ea
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
14
config.go
14
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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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: `<username>[:<password>] 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 <username>[:<password>] 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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
20
sharness/config/basic_auth/service.json
Normal file
20
sharness/config/basic_auth/service.json
Normal file
|
@ -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"
|
||||
}
|
20
sharness/config/ssl-basic_auth/service.json
Normal file
20
sharness/config/ssl-basic_auth/service.json
Normal file
|
@ -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"
|
||||
}
|
24
sharness/config/ssl/server.crt
Normal file
24
sharness/config/ssl/server.crt
Normal file
|
@ -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-----
|
27
sharness/config/ssl/server.key
Normal file
27
sharness/config/ssl/server.key
Normal file
|
@ -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-----
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
test_description="Test service + ctl SSL interaction"
|
||||
|
||||
ssl_config="`pwd`/ssl"
|
||||
ssl_config="`pwd`/config/ssl"
|
||||
|
||||
. lib/test-lib.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
|
||||
'
|
||||
|
|
41
sharness/t0042-basic-auth.sh
Executable file
41
sharness/t0042-basic-auth.sh
Executable file
|
@ -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
|
23
sharness/t0043-ssl-basic-auth.sh
Executable file
23
sharness/t0043-ssl-basic-auth.sh
Executable file
|
@ -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
|
Loading…
Reference in New Issue
Block a user