diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf83361 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +sda-uppmax-integration diff --git a/README.md b/README.md index c07a299..5f6a304 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,19 @@ curl --location --request POST ':8080/token' \ "projectid": "" }' ``` +where `` is the base64 encoded string `username:password`. The `token` endpoint requires basic auth and the allowed credentials can be defined in the configuration file `config.yaml`. +ex. +```bash +$ curl --location --request POST 'localhost:8080/token' \ + --header "Authorization: Basic $(printf 'uppmax:uppmax' | base64)" \ + --header 'Content-Type: application/json' \ + --data-raw '{"swamid": "test@sda.dev", "projectid": "sda001"}' +``` +can be used in the docker compose development environment. + ## Endpoint Response The `token` endpoint returns the following structure, if the user is authorised to access the `` requested: diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..e3cf982 --- /dev/null +++ b/compose.yml @@ -0,0 +1,37 @@ +services: + keymaker: + image: golang:alpine3.16 + volumes: + - keys:/keys + command: + - "/bin/sh" + - "-c" + - if [ ! -f "/keys/c4gh.sec.pem" ]; then git --help >/dev/null 2>&1; [[ "$$?" != "0" ]] && apk add git; + [ ! -d "crypt4gh" ] && git clone https://github.com/neicnordic/crypt4gh.git; + cd crypt4gh; + [ ! -f "crypt4gh" ] && go build .; + ./crypt4gh generate -n c4gh -p 'pass' && mv *.pem /keys/; fi; + [ ! -f /keys/jwt.key ] && apk add openssl && openssl ecparam -name prime256v1 -genkey -noout -out /keys/jwt.key && chmod 644 /keys/jwt.key || true + uppmax-integration: + build: + context: . + depends_on: + keymaker: + condition: service_completed_successfully + environment: + - LOG_LEVEL=debug + - GLOBAL_CRYPT4GHKEY=/keys/c4gh.sec.pem + - GLOBAL_EGAUSER=test@sda.dev + - GLOBAL_EXPIRATIONDAYS=14 + - GLOBAL_ISS=https://login.sda.dev + - GLOBAL_JWTKEY=/keys/jwt.key + - GLOBAL_S3URL=inbox.sda.dev + - GLOBAL_UPPMAXUSERNAME=uppmax + - GLOBAL_UPPMAXPASSWORD=uppmax + volumes: + - keys:/keys + ports: + - 8080:8080 + +volumes: + keys: diff --git a/config.yaml b/config.yaml index 03d46e9..ba929f6 100644 --- a/config.yaml +++ b/config.yaml @@ -7,3 +7,7 @@ global: s3url: "" uppmaxUsername: "" uppmaxPassword: "" + +log: + format: text + level: debug diff --git a/helpers/helpers.go b/helpers/helpers.go index 04ac19e..46c260c 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -68,10 +68,28 @@ func NewConf(conf *Conf) (err error) { for _, s := range requiredConfVars { if !viper.IsSet(s) || viper.GetString(s) == "" { - return fmt.Errorf("Required configuration field %s not set", s) + return fmt.Errorf("required configuration field %s not set", s) } } + if viper.IsSet("log.format") { + if viper.GetString("log.format") == "json" { + log.SetFormatter(&log.JSONFormatter{}) + log.Info("The logs format is set to JSON") + } + } + + if viper.IsSet("log.level") { + stringLevel := viper.GetString("log.level") + intLevel, err := log.ParseLevel(stringLevel) + if err != nil { + log.Printf("Log level '%s' not supported, setting to 'trace'", stringLevel) + intLevel = log.TraceLevel + } + log.SetLevel(intLevel) + log.Printf("Setting log level to '%s'", stringLevel) + } + conf.Iss = viper.GetString("global.iss") conf.JwtKeyPath = viper.GetString("global.jwtKey") conf.Username = viper.GetString("global.uppmaxUsername") @@ -87,12 +105,12 @@ func NewConf(conf *Conf) (err error) { } conf.JwtParsedKey, err = parsePrivateECKey(conf.JwtKeyPath) if err != nil { - return fmt.Errorf("Could not parse ec key") + return fmt.Errorf("could not parse ec key: %v", err) } // Parse crypt4gh key and store it as base64 encoded keyBytes, err := os.ReadFile(conf.Crypt4ghKeyPath) if err != nil { - return fmt.Errorf("Could not parse crypt4gh public key") + return fmt.Errorf("could not parse crypt4gh public key: %v", err) } conf.Crypt4ghKey = b64.StdEncoding.EncodeToString([]byte(keyBytes)) diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index 16fd079..f061439 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -88,7 +88,7 @@ func (suite *TestSuite) TestNewConfMissingValue() { } err = NewConf(&Config) - assert.EqualError(suite.T(), err, "Required configuration field global.iss not set") + assert.EqualError(suite.T(), err, "required configuration field global.iss not set") defer os.Remove(configName) } @@ -111,7 +111,7 @@ func (suite *TestSuite) TestNewConfMissingKey() { } err = NewConf(&Config) - assert.EqualError(suite.T(), err, "Could not parse ec key") + assert.EqualError(suite.T(), err, "could not parse ec key: open some/path: no such file or directory") defer os.Remove(configName) } diff --git a/main.go b/main.go index 358eee0..0cf3832 100644 --- a/main.go +++ b/main.go @@ -21,18 +21,20 @@ func main() { log.Fatal(err) } + servicePort := 8080 + http.HandleFunc("/token", helpers.BasicAuth(token.GetToken)) http.HandleFunc("/ping", ping) server := &http.Server{ - Addr: ":8000", + Addr: fmt.Sprintf(":%v", servicePort), ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 30 * time.Second, ReadHeaderTimeout: 30 * time.Second, } - fmt.Println("Starting server at port 8080") + log.Infof("Starting server at port %v", servicePort) log.Fatal(server.ListenAndServe()) } diff --git a/token/token.go b/token/token.go index ee14485..0e14415 100644 --- a/token/token.go +++ b/token/token.go @@ -36,7 +36,7 @@ func readRequestBody(body io.ReadCloser) (tokenRequest tokenRequest, err error) if err != nil { log.Print("Error reading request body: ", err) - return tokenRequest, fmt.Errorf("Error reading request body") + return tokenRequest, fmt.Errorf("error reading request body") } defer body.Close() @@ -44,11 +44,11 @@ func readRequestBody(body io.ReadCloser) (tokenRequest tokenRequest, err error) if err != nil { log.Print("Error unmarshaling: ", err) - return tokenRequest, fmt.Errorf("Error unmarshaling data") + return tokenRequest, fmt.Errorf("error unmarshaling data") } if tokenRequest.ProjectID == "" || tokenRequest.Swamid == "" { - return tokenRequest, fmt.Errorf("Incomplete incoming data") + return tokenRequest, fmt.Errorf("incomplete incoming data") } return tokenRequest, nil @@ -107,7 +107,7 @@ func createResponse(tokenRequest tokenRequest, username string) (tokenResponse t tokenResponse.S3Config, tokenResponse.Expiration, err = createS3Config(username) if err != nil { - return tokenResponse, fmt.Errorf("Error creating S3 configuration") + return tokenResponse, fmt.Errorf("error creating S3 configuration") } return tokenResponse, err diff --git a/token/token_test.go b/token/token_test.go index 1acb7bc..cfafbc8 100644 --- a/token/token_test.go +++ b/token/token_test.go @@ -66,7 +66,7 @@ func (suite *TestSuite) TestNewConf() { _, err = readRequestBody(r) - assert.EqualError(suite.T(), err, "Error unmarshaling data") + assert.EqualError(suite.T(), err, "error unmarshaling data") r = io.NopCloser(strings.NewReader(`{ "swami": "", @@ -75,7 +75,7 @@ func (suite *TestSuite) TestNewConf() { _, err = readRequestBody(r) - assert.EqualError(suite.T(), err, "Incomplete incoming data") + assert.EqualError(suite.T(), err, "incomplete incoming data") } func (suite *TestSuite) TestCreateECToken() {