diff --git a/auth/center.go b/api/auth/center.go similarity index 56% rename from auth/center.go rename to api/auth/center.go index de14d1aa..b6752415 100644 --- a/auth/center.go +++ b/api/auth/center.go @@ -3,7 +3,8 @@ package auth import ( "bytes" "context" - "crypto/ecdsa" + "crypto/sha256" + "encoding/hex" "fmt" "io/ioutil" "net/http" @@ -13,10 +14,11 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + sdk "github.com/nspcc-dev/cdn-neofs-sdk" + "github.com/nspcc-dev/cdn-neofs-sdk/creds/accessbox" + "github.com/nspcc-dev/cdn-neofs-sdk/creds/hcs" + "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/token" - "github.com/nspcc-dev/neofs-authmate/accessbox/hcs" - "github.com/nspcc-dev/neofs-authmate/agents/s3" - manager "github.com/nspcc-dev/neofs-authmate/manager/neofs" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -24,92 +26,116 @@ import ( var authorizationFieldRegexp = regexp.MustCompile(`AWS4-HMAC-SHA256 Credential=(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]*)/(?P[^/]+)/aws4_request,\s*SignedHeaders=(?P.+),\s*Signature=(?P.+)`) type ( - Center struct { - man *manager.Manager - reg *regexpSubmatcher + Center interface { + Authenticate(request *http.Request) (*token.BearerToken, error) + } - keys *hcs.X25519Keys + center struct { + cli sdk.Client + key hcs.PrivateKey + reg *regexpSubmatcher } Params struct { - Timeout time.Duration - - Log *zap.Logger - Con manager.Connector - - GAKey *hcs.X25519Keys - NFKey *ecdsa.PrivateKey + Client sdk.Client + Logger *zap.Logger + Credential hcs.Credentials } ) // New creates an instance of AuthCenter. -func New(ctx context.Context, p *Params) (*Center, error) { - m, err := manager.New(ctx, - manager.WithKey(p.NFKey), - manager.WithLogger(p.Log), - manager.WithConnector(p.Con)) - - if err != nil { - return nil, err - } - return &Center{ - man: m, +func New(cli sdk.Client, key hcs.PrivateKey) Center { + return ¢er{ + cli: cli, + key: key, reg: ®expSubmatcher{re: authorizationFieldRegexp}, - - keys: p.GAKey, - }, nil + } } -func (center *Center) AuthenticationPassed(request *http.Request) (*token.BearerToken, error) { - queryValues := request.URL.Query() +func (c *center) Authenticate(r *http.Request) (*token.BearerToken, error) { + queryValues := r.URL.Query() if queryValues.Get("X-Amz-Algorithm") == "AWS4-HMAC-SHA256" { return nil, errors.New("pre-signed form of request is not supported") } - authHeaderField := request.Header["Authorization"] + + authHeaderField := r.Header["Authorization"] if len(authHeaderField) != 1 { return nil, errors.New("unsupported request: wrong length of Authorization header field") } - sms1 := center.reg.getSubmatches(authHeaderField[0]) + + sms1 := c.reg.getSubmatches(authHeaderField[0]) if len(sms1) != 7 { return nil, errors.New("bad Authorization header field") } + signedHeaderFieldsNames := strings.Split(sms1["signed_header_fields"], ";") if len(signedHeaderFieldsNames) == 0 { return nil, errors.New("wrong format of signed headers part") } - signatureDateTime, err := time.Parse("20060102T150405Z", request.Header.Get("X-Amz-Date")) + + signatureDateTime, err := time.Parse("20060102T150405Z", r.Header.Get("X-Amz-Date")) if err != nil { return nil, errors.Wrap(err, "failed to parse x-amz-date header field") } + accessKeyID := fmt.Sprintf("%s/%s", sms1["access_key_id_cid"], sms1["access_key_id_oid"]) - res, err := s3.NewAgent(center.man).ObtainSecret(request.Context(), center.keys, accessKeyID) - if err != nil { - return nil, errors.Wrap(err, "failed to fetch bearer token") + + address := object.NewAddress() + if err = address.Parse(accessKeyID); err != nil { + return nil, errors.Wrapf(err, "could not parse AccessBox address: %s", accessKeyID) } - otherRequest := request.Clone(context.TODO()) + + buf := new(bytes.Buffer) + if _, err = c.cli.Object().Get(r.Context(), address, sdk.WithGetWriter(buf)); err != nil { + return nil, errors.Wrapf(err, "could not fetch AccessBox: %s (%s / %s)", + accessKeyID, + address.ContainerID(), + address.ObjectID()) + } + + box := accessbox.NewBearerBox(nil) + if err = accessbox.NewDecoder(buf, c.key).Decode(box); err != nil { + return nil, err + } + + data, err := box.Token().Marshal() + if err != nil { + return nil, err + } + + hash := sha256.Sum256(data) + secret := hex.EncodeToString(hash[:]) + + otherRequest := r.Clone(context.TODO()) otherRequest.Header = map[string][]string{} - for hfn, hfvs := range request.Header { + + for hfn, hfvs := range r.Header { for _, shfn := range signedHeaderFieldsNames { if strings.EqualFold(hfn, shfn) { otherRequest.Header[hfn] = hfvs } } } - awsCreds := credentials.NewStaticCredentials(accessKeyID, res.SecretAccessKey, "") + + awsCreds := credentials.NewStaticCredentials(accessKeyID, secret, "") signer := v4.NewSigner(awsCreds) - body, err := readAndKeepBody(request) + + body, err := readAndKeepBody(r) if err != nil { return nil, errors.Wrap(err, "failed to read out request body") } - _, err = signer.Sign(otherRequest, body, sms1["service"], sms1["region"], signatureDateTime) + + hdr, err := signer.Sign(otherRequest, body, sms1["service"], sms1["region"], signatureDateTime) if err != nil { return nil, errors.Wrap(err, "failed to sign temporary HTTP request") } - sms2 := center.reg.getSubmatches(otherRequest.Header.Get("Authorization")) + + sms2 := c.reg.getSubmatches(hdr.Get("Authorization")) if sms1["v4_signature"] != sms2["v4_signature"] { return nil, errors.Wrap(err, "failed to pass authentication procedure") } - return res.BearerToken, nil + + return box.Token(), nil } // TODO: Make this write into a smart buffer backed by a file on a fast drive. diff --git a/api/auth/regexp-utils.go b/api/auth/regexp-utils.go new file mode 100644 index 00000000..1e9b2dde --- /dev/null +++ b/api/auth/regexp-utils.go @@ -0,0 +1,20 @@ +package auth + +import "regexp" + +type regexpSubmatcher struct { + re *regexp.Regexp +} + +func (r *regexpSubmatcher) getSubmatches(target string) map[string]string { + matches := r.re.FindStringSubmatch(target) + l := len(matches) + + sub := make(map[string]string, l) + for i, name := range r.re.SubexpNames() { + if i > 0 && i <= l { + sub[name] = matches[i] + } + } + return sub +} diff --git a/auth/bearer-token.go b/auth/bearer-token.go deleted file mode 100644 index a7ef9865..00000000 --- a/auth/bearer-token.go +++ /dev/null @@ -1,30 +0,0 @@ -package auth - -import ( - "context" - - "github.com/nspcc-dev/neofs-api-go/pkg/token" - "github.com/pkg/errors" -) - -type contextKey string - -const bearerTokenContextKey contextKey = "bearer-token" - -// GetBearerToken returns a bearer token embedded into a context or error, if any. -func GetBearerToken(ctx context.Context) (*token.BearerToken, error) { - bt := ctx.Value(bearerTokenContextKey) - if bt == nil { - return nil, errors.New("got nil bearer token") - } - v, ok := bt.(*token.BearerToken) - if !ok { - return nil, errors.Errorf("extracted unexpected type other than bearer token's: %T", v) - } - return v, nil -} - -// SetBearerToken return a context with embedded bearer token. -func SetBearerToken(ctx context.Context, bearerToken *token.BearerToken) context.Context { - return context.WithValue(ctx, bearerTokenContextKey, bearerToken) -} diff --git a/auth/regexp-utils.go b/auth/regexp-utils.go deleted file mode 100644 index 94ba85d1..00000000 --- a/auth/regexp-utils.go +++ /dev/null @@ -1,19 +0,0 @@ -package auth - -import "regexp" - -type regexpSubmatcher struct { - re *regexp.Regexp -} - -func (resm *regexpSubmatcher) getSubmatches(target string) map[string]string { - matches := resm.re.FindStringSubmatch(target) - l := len(matches) - submatches := make(map[string]string, l) - for i, name := range resm.re.SubexpNames() { - if i > 0 && i <= l { - submatches[name] = matches[i] - } - } - return submatches -}