X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/1a7c5c627ca9cbbbc13e1c9710bbd6268c59b22a..18d976b4701d76bdeb05e0fe3c1757060d3b8a2a:/lib/controller/federation/conn.go diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go index 586ac23013..d9f587852d 100644 --- a/lib/controller/federation/conn.go +++ b/lib/controller/federation/conn.go @@ -22,6 +22,7 @@ import ( "git.arvados.org/arvados.git/sdk/go/arvados" "git.arvados.org/arvados.git/sdk/go/auth" "git.arvados.org/arvados.git/sdk/go/ctxlog" + "git.arvados.org/arvados.git/sdk/go/health" ) type Conn struct { @@ -30,20 +31,25 @@ type Conn struct { remotes map[string]backend } -func New(cluster *arvados.Cluster) *Conn { +func New(cluster *arvados.Cluster, healthFuncs *map[string]health.Func) *Conn { local := localdb.NewConn(cluster) remotes := map[string]backend{} for id, remote := range cluster.RemoteClusters { if !remote.Proxy || id == cluster.ClusterID { continue } - conn := rpc.NewConn(id, &url.URL{Scheme: remote.Scheme, Host: remote.Host}, remote.Insecure, saltedTokenProvider(local, id)) + conn := rpc.NewConn(id, &url.URL{Scheme: remote.Scheme, Host: remote.Host}, remote.Insecure, saltedTokenProvider(cluster, local, id)) // Older versions of controller rely on the Via header // to detect loops. conn.SendHeader = http.Header{"Via": {"HTTP/1.1 arvados-controller"}} remotes[id] = conn } + if healthFuncs != nil { + hf := map[string]health.Func{"vocabulary": local.LastVocabularyError} + *healthFuncs = hf + } + return &Conn{ cluster: cluster, local: local, @@ -55,7 +61,7 @@ func New(cluster *arvados.Cluster) *Conn { // tokens from an incoming request context, determines whether they // should (and can) be salted for the given remoteID, and returns the // resulting tokens. -func saltedTokenProvider(local backend, remoteID string) rpc.TokenProvider { +func saltedTokenProvider(cluster *arvados.Cluster, local backend, remoteID string) rpc.TokenProvider { return func(ctx context.Context) ([]string, error) { var tokens []string incoming, ok := auth.FromContext(ctx) @@ -63,6 +69,19 @@ func saltedTokenProvider(local backend, remoteID string) rpc.TokenProvider { return nil, errors.New("no token provided") } for _, token := range incoming.Tokens { + if strings.HasPrefix(token, "v2/"+cluster.ClusterID+"-") && + !strings.HasPrefix(token, "v2/"+cluster.ClusterID+"-gj3su-anonymouspublic/") && + remoteID == cluster.Login.LoginCluster { + // If we did this, the login cluster would call back to us and then + // reject our response because the user UUID prefix (i.e., the + // LoginCluster prefix) won't match the token UUID prefix (i.e., our + // prefix). The anonymous token is OK to forward, because (unlike other + // local tokens for real users) the validation callback will return the + // locally issued anonymous user ID instead of a login-cluster user ID. + // That anonymous user ID gets mapped to the local anonymous user + // automatically on the login cluster. + return nil, httpErrorf(http.StatusUnauthorized, "cannot use a locally issued token to forward a request to our login cluster (%s)", remoteID) + } salted, err := auth.SaltToken(token, remoteID) switch err { case nil: @@ -192,6 +211,10 @@ func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) { return json.RawMessage(buf.Bytes()), err } +func (conn *Conn) VocabularyGet(ctx context.Context) (arvados.Vocabulary, error) { + return conn.chooseBackend(conn.cluster.ClusterID).VocabularyGet(ctx) +} + func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) { if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID { // defer entire login procedure to designated cluster @@ -262,13 +285,26 @@ func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) if err != nil { return err } - // options.UUID is either hash+size or - // hash+size+hints; only hash+size need to - // match the computed PDH. - if pdh := arvados.PortableDataHash(c.ManifestText); pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") { - err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID) - ctxlog.FromContext(ctx).Warn(err) - return err + haveManifest := true + if options.Select != nil { + haveManifest = false + for _, s := range options.Select { + if s == "manifest_text" { + haveManifest = true + break + } + } + } + if haveManifest { + pdh := arvados.PortableDataHash(c.ManifestText) + // options.UUID is either hash+size or + // hash+size+hints; only hash+size need to + // match the computed PDH. + if pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") { + err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID) + ctxlog.FromContext(ctx).Warn(err) + return err + } } if remoteID != "" { c.ManifestText = rewriteManifest(c.ManifestText, remoteID) @@ -452,6 +488,26 @@ func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptio return conn.chooseBackend(options.UUID).GroupUntrash(ctx, options) } +func (conn *Conn) LinkCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Link, error) { + return conn.chooseBackend(options.ClusterID).LinkCreate(ctx, options) +} + +func (conn *Conn) LinkUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Link, error) { + return conn.chooseBackend(options.UUID).LinkUpdate(ctx, options) +} + +func (conn *Conn) LinkGet(ctx context.Context, options arvados.GetOptions) (arvados.Link, error) { + return conn.chooseBackend(options.UUID).LinkGet(ctx, options) +} + +func (conn *Conn) LinkList(ctx context.Context, options arvados.ListOptions) (arvados.LinkList, error) { + return conn.generated_LinkList(ctx, options) +} + +func (conn *Conn) LinkDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Link, error) { + return conn.chooseBackend(options.UUID).LinkDelete(ctx, options) +} + func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) { return conn.generated_SpecimenList(ctx, options) } @@ -472,6 +528,10 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options) } +func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) { + return conn.local.SysTrashSweep(ctx, options) +} + var userAttrsCachedFromLoginCluster = map[string]bool{ "created_at": true, "email": true, @@ -493,6 +553,8 @@ var userAttrsCachedFromLoginCluster = map[string]bool{ "owner_uuid": false, "uuid": false, "writable_by": false, + "can_write": false, + "can_manage": false, } func (conn *Conn) batchUpdateUsers(ctx context.Context, @@ -672,6 +734,39 @@ func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arv return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options) } +func (conn *Conn) APIClientAuthorizationCreate(ctx context.Context, options arvados.CreateOptions) (arvados.APIClientAuthorization, error) { + if conn.cluster.Login.LoginCluster != "" { + return conn.chooseBackend(conn.cluster.Login.LoginCluster).APIClientAuthorizationCreate(ctx, options) + } + ownerUUID, ok := options.Attrs["owner_uuid"].(string) + if ok && ownerUUID != "" { + return conn.chooseBackend(ownerUUID).APIClientAuthorizationCreate(ctx, options) + } + return conn.local.APIClientAuthorizationCreate(ctx, options) +} + +func (conn *Conn) APIClientAuthorizationUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.APIClientAuthorization, error) { + if options.BypassFederation { + return conn.local.APIClientAuthorizationUpdate(ctx, options) + } + return conn.chooseBackend(options.UUID).APIClientAuthorizationUpdate(ctx, options) +} + +func (conn *Conn) APIClientAuthorizationDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.APIClientAuthorization, error) { + return conn.chooseBackend(options.UUID).APIClientAuthorizationDelete(ctx, options) +} + +func (conn *Conn) APIClientAuthorizationList(ctx context.Context, options arvados.ListOptions) (arvados.APIClientAuthorizationList, error) { + if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID && !options.BypassFederation { + return conn.chooseBackend(conn.cluster.Login.LoginCluster).APIClientAuthorizationList(ctx, options) + } + return conn.generated_APIClientAuthorizationList(ctx, options) +} + +func (conn *Conn) APIClientAuthorizationGet(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) { + return conn.chooseBackend(options.UUID).APIClientAuthorizationGet(ctx, options) +} + type backend interface { arvados.API BaseURL() url.URL