diff --git a/origin/advertise.go b/origin/advertise.go index fa9272622..a76435435 100644 --- a/origin/advertise.go +++ b/origin/advertise.go @@ -199,3 +199,19 @@ func (server *OriginServer) GetAuthorizedPrefixes() ([]string, error) { return prefixes, nil } + +func (server *OriginServer) GetPublicReadOnlyPrefixes() ([]string, error) { + var prefixes []string + originExports, err := server_utils.GetOriginExports() + if err != nil { + return nil, err + } + + for _, export := range originExports { + if export.Capabilities.PublicReads && !export.Capabilities.DirectReads && !export.Capabilities.Writes { + prefixes = append(prefixes, export.FederationPrefix) + } + } + + return prefixes, nil +} diff --git a/xrootd/authorization.go b/xrootd/authorization.go index 37e71d09a..4f3d771af 100644 --- a/xrootd/authorization.go +++ b/xrootd/authorization.go @@ -69,6 +69,7 @@ type ( DefaultUser string UsernameClaim string NameMapfile string + FedIssuer bool } // Top-level configuration object for the template @@ -299,7 +300,7 @@ func EmitAuthfile(server server_structs.XRootDServer) error { } for _, export := range originExports { - if export.Capabilities.PublicReads { + if export.Capabilities.DirectReads { outStr += export.FederationPrefix + " lr " } } @@ -314,7 +315,7 @@ func EmitAuthfile(server server_structs.XRootDServer) error { output.Write([]byte(lineContents + "\n")) } } - // If Origin has no authfile already exists, add the ./well-known to the authfile + // If Origin has no authfile already, add the ./well-known to the authfile if !foundPublicLine && server.GetServerType().IsEnabled(server_structs.OriginType) { outStr := "u * /.well-known lr" @@ -325,7 +326,7 @@ func EmitAuthfile(server server_structs.XRootDServer) error { } for _, export := range originExports { - if export.Capabilities.PublicReads { + if export.Capabilities.DirectReads { outStr += " " + export.FederationPrefix + " lr" } } @@ -482,6 +483,19 @@ func GenerateOriginIssuer(exportedPaths []string) (issuer Issuer, err error) { return } +func GenerateFederationIssuer(authPaths []string, publicPaths []string) (issuer Issuer) { + if len(authPaths) == 0 && len(publicPaths) == 0 { + return + } + + issuer.Name = "Registry" + issuer.Issuer = param.Federation_DiscoveryUrl.GetString() + issuer.BasePaths = append(authPaths, publicPaths...) + issuer.FedIssuer = true + + return +} + // We have a special issuer just for director-based monitoring of the origin. func GenerateDirectorMonitoringIssuer() (issuer Issuer, err error) { fedInfo, err := config.GetFederation(context.Background()) @@ -536,7 +550,11 @@ func EmitScitokensConfig(server server_structs.XRootDServer) error { if err != nil { return err } - return WriteOriginScitokensConfig(authedPrefixes) + publicReadsPrefixes, err := originServer.GetPublicReadOnlyPrefixes() + if err != nil { + return err + } + return WriteOriginScitokensConfig(authedPrefixes, publicReadsPrefixes) } else if cacheServer, ok := server.(*cache.CacheServer); ok { directorAds := cacheServer.GetNamespaceAds() if param.Cache_SelfTest.GetBool() { @@ -564,7 +582,7 @@ func EmitScitokensConfig(server server_structs.XRootDServer) error { } // Writes out the origin's scitokens.cfg configuration -func WriteOriginScitokensConfig(authedPaths []string) error { +func WriteOriginScitokensConfig(authedPaths []string, publicReadPaths []string) error { cfg, err := makeSciTokensCfg() if err != nil { return err @@ -585,6 +603,18 @@ func WriteOriginScitokensConfig(authedPaths []string) error { return errors.Wrap(err, "failed to generate xrootd issuer for the origin") } + if issuer := GenerateFederationIssuer(authedPaths, publicReadPaths); len(issuer.Name) > 0 { + if val, ok := cfg.IssuerMap[issuer.Issuer]; ok { + val.BasePaths = append(val.BasePaths, issuer.BasePaths...) + val.Name += " and " + issuer.Name + cfg.IssuerMap[issuer.Issuer] = val + } else { + cfg.IssuerMap[issuer.Issuer] = issuer + } + } else if err != nil { + return errors.Wrap(err, "failed to generate xrootd registry issuer for the origin") + } + if issuer, err := GenerateMonitoringIssuer(); err == nil && len(issuer.Name) > 0 { if val, ok := cfg.IssuerMap[issuer.Issuer]; ok { val.BasePaths = append(val.BasePaths, issuer.BasePaths...) diff --git a/xrootd/authorization_test.go b/xrootd/authorization_test.go index a5cc39e42..2e3e4f43f 100644 --- a/xrootd/authorization_test.go +++ b/xrootd/authorization_test.go @@ -361,6 +361,68 @@ func TestEmitAuthfile(t *testing.T) { } } +func TestEmitOriginAuthfileWithCapabilities(t *testing.T) { + tests := []struct { + desc string + name string + authOut string + capabilities []string + }{ + { + desc: "public-reads", + name: "/public", + authOut: "u * /.well-known lr\n", + capabilities: []string{"Origin.EnablePublicReads"}, + }, + { + desc: "direct-reads", + name: "/direct", + authOut: "u * /.well-known lr /direct lr\n", + capabilities: []string{"Origin.EnableDirectReads"}, + }, + { + desc: "direct-and-public-reads", + name: "/direct-public", + authOut: "u * /.well-known lr /direct-public lr\n", + capabilities: []string{"Origin.EnablePublicReads", "Origin.EnableDirectReads"}, + }, + { + desc: "no-public-access", + name: "/private", + authOut: "u * /.well-known lr\n", + capabilities: []string{"Origin.EnableReads"}, + }, + } + for _, testInput := range tests { + t.Run(testInput.desc, func(t *testing.T) { + dirName := t.TempDir() + server_utils.ResetTestState() + + defer server_utils.ResetTestState() + + viper.Set("Xrootd.Authfile", filepath.Join(dirName, "authfile")) + viper.Set("Origin.RunLocation", dirName) + viper.Set("Origin.FederationPrefix", testInput.name) + viper.Set("Origin.StoragePrefix", "/") + for _, cap := range testInput.capabilities { + viper.Set(cap, true) + } + originServer := &origin.OriginServer{} + + err := os.WriteFile(filepath.Join(dirName, "authfile"), []byte(""), fs.FileMode(0600)) + require.NoError(t, err) + + err = EmitAuthfile(originServer) + require.NoError(t, err) + + contents, err := os.ReadFile(filepath.Join(dirName, "authfile-origin-generated")) + require.NoError(t, err) + + assert.Equal(t, testInput.authOut, string(contents)) + }) + } +} + func TestEmitCfg(t *testing.T) { dirname := t.TempDir() server_utils.ResetTestState() @@ -611,7 +673,7 @@ func TestWriteOriginAuthFiles(t *testing.T) { t.Run("EmptyAuth", originAuthTester(originServer, "", "u * /.well-known lr\n")) - viper.Set("Origin.EnablePublicReads", true) + viper.Set("Origin.EnableDirectReads", true) viper.Set("Origin.FederationPrefix", "/foo/bar") t.Run("PublicAuth", originAuthTester(originServer, "", "u * /.well-known lr /foo/bar lr\n")) } @@ -753,7 +815,9 @@ func TestWriteOriginScitokensConfig(t *testing.T) { err = os.WriteFile(scitokensCfg, []byte(toMergeOutput), 0640) require.NoError(t, err) - err = WriteOriginScitokensConfig([]string{"/foo/bar"}) + viper.Set("Federation.DiscoveryUrl", "fed.discovery.com") + + err = WriteOriginScitokensConfig([]string{"/foo/bar"}, []string{"/public"}) require.NoError(t, err) genCfg, err := os.ReadFile(filepath.Join(dirname, "scitokens-origin-generated.cfg")) diff --git a/xrootd/resources/scitokens.cfg b/xrootd/resources/scitokens.cfg index 174e8438f..c648b856a 100644 --- a/xrootd/resources/scitokens.cfg +++ b/xrootd/resources/scitokens.cfg @@ -41,6 +41,10 @@ name_mapfile = {{.NameMapfile}} {{- if .UsernameClaim}} username_claim = {{.UsernameClaim}} {{- end}} +{{- if .FedIssuer}} +required_authorization = all +acceptable_authorization = none +{{- end}} {{end -}} # End of config diff --git a/xrootd/resources/test-scitokens-monitoring.cfg b/xrootd/resources/test-scitokens-monitoring.cfg index d80ad4eb0..b38781e4e 100644 --- a/xrootd/resources/test-scitokens-monitoring.cfg +++ b/xrootd/resources/test-scitokens-monitoring.cfg @@ -22,6 +22,12 @@ [Global] audience_json = ["test_audience","https://origin.example.com:8443"] +[Issuer Registry] +issuer = fed.discovery.com +base_path = /foo/bar, /public +required_authorization = all +acceptable_authorization = none + [Issuer Demo] issuer = https://demo.scitokens.org base_path = /bar, /foo diff --git a/xrootd/xrootd_config.go b/xrootd/xrootd_config.go index ce404a1da..bb00f6f56 100644 --- a/xrootd/xrootd_config.go +++ b/xrootd/xrootd_config.go @@ -275,7 +275,11 @@ func CheckOriginXrootdEnv(exportPath string, server server_structs.XRootDServer, if err != nil { return err } - err = WriteOriginScitokensConfig(authedPrefixes) + publicReadPrefixes, err := originServer.GetPublicReadOnlyPrefixes() + if err != nil { + return err + } + err = WriteOriginScitokensConfig(authedPrefixes, publicReadPrefixes) if err != nil { return err } diff --git a/xrootd/xrootd_config_test.go b/xrootd/xrootd_config_test.go index 70966138c..08b26329a 100644 --- a/xrootd/xrootd_config_test.go +++ b/xrootd/xrootd_config_test.go @@ -630,6 +630,7 @@ func TestUpdateAuth(t *testing.T) { viper.Set("Xrootd.ScitokensConfig", scitokensName) viper.Set("Origin.FederationPrefix", "/test") viper.Set("Origin.StoragePrefix", "/") + viper.Set("Origin.EnableDirectReads", false) config.InitConfig() err := config.InitServer(ctx, server_structs.OriginType)