Merge branch 'master' into 14716-webdav-cluster-config
authorLucas Di Pentima <ldipentima@veritasgenetics.com>
Tue, 6 Aug 2019 19:36:27 +0000 (16:36 -0300)
committerLucas Di Pentima <ldipentima@veritasgenetics.com>
Tue, 6 Aug 2019 19:36:27 +0000 (16:36 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <ldipentima@veritasgenetics.com>

20 files changed:
doc/admin/upgrading.html.textile.liquid
doc/install/install-keep-web.html.textile.liquid
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/export.go
lib/config/generated_config.go
lib/config/load.go
sdk/go/arvados/config.go
sdk/python/tests/run_test_server.py
services/keep-web/cache.go
services/keep-web/cache_test.go
services/keep-web/cadaver_test.go
services/keep-web/doc.go
services/keep-web/handler.go
services/keep-web/handler_test.go
services/keep-web/main.go
services/keep-web/server.go
services/keep-web/server_test.go
services/keep-web/status_test.go
services/keep-web/usage.go [deleted file]

index da37d743de4aa43d821ff449f7a21d772af73bb2..de7eeaca048e2b63fe5e282b20d50d5ee48267ed 100644 (file)
@@ -39,7 +39,11 @@ table(table table-bordered table-condensed).
 |"v1.1.4":#v1_1_4|"v1.1.3":#v1_1_3|"v1.1.2":#v1_1_2|"v1.1.1":#v1_1_1|"v1.1.0":#v1_1_0|
 |\5. "older":#older|
 
-h3(#master). development master (as of 2019-06-07)
+h3(#master). development master (as of 2019-08-05)
+
+h4. Keep-web dropped support on command line flags configuration
+
+As we're migrating to a central cluster configuration file, the already deprecated way of getting configurations via environment variables and command line flags isn't valid anymore. Current keep-web supports both the now legacy @keep-web.yml@ config format (used by Arvados 1.4) and the new cluster config file format. Please check "keep-web's install guide":{{site.baseurl}}/install/install-keep-web.html for more details.
 
 h4. No longer stripping ':' from strings in serialized database columns
 
index 2991d7b0dc2df95aef92aaf191856067d3362adf..08dc186e8da41ec89cc834dfbc3c4cc56262234b 100644 (file)
@@ -45,14 +45,22 @@ Verify that @Keep-web@ is functional:
 <notextile>
 <pre><code>~$ <span class="userinput">keep-web -h</span>
 Usage of keep-web:
-  -allow-anonymous
-        Serve public data to anonymous clients. Try the token supplied in the ARVADOS_API_TOKEN environment variable when none of the tokens provided in an HTTP request succeed in reading the desired collection. (default false)
-  -attachment-only-host string
-        Accept credentials, and add "Content-Disposition: attachment" response headers, for requests at this hostname:port. Prohibiting inline display makes it possible to serve untrusted and non-public content from a single origin, i.e., without wildcard DNS or TLS.
-  -listen string
-        Address to listen on: "host:port", or ":port" to listen on all interfaces. (default ":80")
-  -trust-all-content
-        Serve non-public content from a single origin. Dangerous: read docs before using!
+  -config file
+       Site configuration file (default may be overridden by setting an ARVADOS_CONFIG environment variable) (default "/etc/arvados/config.yml")
+  -dump-config
+       write current configuration to stdout and exit
+  -legacy-crunch-dispatch-slurm-config file
+       Legacy crunch-dispatch-slurm configuration file (default "/etc/arvados/crunch-dispatch-slurm/crunch-dispatch-slurm.yml")
+  -legacy-keepstore-config file
+       Legacy keepstore configuration file (default "/etc/arvados/keepstore/keepstore.yml")
+  -legacy-keepweb-config file
+       Legacy keep-web configuration file (default "/etc/arvados/keep-web/keep-web.yml")
+  -legacy-ws-config file
+       Legacy arvados-ws configuration file (default "/etc/arvados/ws/ws.yml")
+  -skip-legacy
+       Don't load legacy config files
+  -version
+       print version information and exit.
 </code></pre>
 </notextile>
 
@@ -62,22 +70,40 @@ If you intend to use Keep-web to serve public data to anonymous clients, configu
 
 Install runit to supervise the Keep-web daemon.  {% include 'install_runit' %}
 
+Set the cluster config file like the following:
+
+<notextile>
+<pre><code>Clusters:
+  <span class="userinput">uuid_prefix</span>:
+    SystemRootToken: "{{railsout}}"
+    Services:
+      Controller:
+        ExternalURL: "https://<span class="userinput">uuid_prefix</span>.your.domain"
+        Insecure: false
+      WebDAV:
+        InternalURLs:
+          "http://:9002/": {}
+      WebDAVDownload:
+        InternalURLs:
+          "http://:9002/": {}
+        ExternalURL: "https://download.<span class="userinput">uuid_prefix</span>.your.domain/"
+    Users:
+      AnonymousUserToken: "xxxxxxxxxxxxxxxxxxxx"
+    Collections:
+      TrustAllContent: false
+</code></pre>
+</notextile>
+
 The basic command to start Keep-web in the service run script is:
 
 <notextile>
-<pre><code>export ARVADOS_API_HOST=<span class="userinput">uuid_prefix</span>.your.domain
-export ARVADOS_API_TOKEN="<span class="userinput">{{railsout}}</span>"
-exec sudo -u nobody keep-web \
- -listen=<span class="userinput">:9002</span> \
- -attachment-only-host=<span class="userinput">download.uuid_prefix.your.domain</span> \
- -allow-anonymous \
- 2&gt;&amp;1
+<pre><code>exec sudo -u nobody keep-web -config=/path/to/arvados.yml
 </code></pre>
 </notextile>
 
-Omit the @-allow-anonymous@ argument if you do not want to serve public data.
+Set @Users.AnonymousUserToken: ""@ (empty string) if you do not want to serve public data.
 
-Set @ARVADOS_API_HOST_INSECURE=1@ if your API server's TLS certificate is not signed by a recognized CA.
+Set @Services.Controller.Insecure: true@ if your API server's TLS certificate is not signed by a recognized CA.
 
 h3. Set up a reverse proxy with TLS support
 
@@ -134,24 +160,32 @@ Configure your DNS servers so the following names resolve to your Nginx proxy's
 
 If neither of the above wildcard options is feasible, you have two choices:
 # Serve web content at @collections.uuid_prefix.your.domain@, but only for unauthenticated requests (public data and collection sharing links). Authenticated requests will always result in file downloads, using the @download@ name. For example, the Workbench "preview" button and the "view entire log file" link will invoke file downloads instead of displaying content in the browser window.
-# In the special case where you know you are immune to XSS exploits, you can enable the "trust all content" mode in Keep-web (with the @-trust-all-content@ command line flag) and Workbench (with the @trust_all_content@ item in @application.yml@). With both of these enabled, inline web content can be served from a single @collections@ host name; no wildcard DNS or certificate is needed. Do not do this without understanding the security implications described in the "Keep-web documentation":http://godoc.org/github.com/curoverse/arvados/services/keep-web.
+# In the special case where you know you are immune to XSS exploits, you can enable the "trust all content" mode in Keep-web and Workbench (setting @Collections.TrustAllContent: true@ on the config file). With this enabled, inline web content can be served from a single @collections@ host name; no wildcard DNS or certificate is needed. Do not do this without understanding the security implications described in the "Keep-web documentation":http://godoc.org/github.com/curoverse/arvados/services/keep-web.
 
 h3. Tell Workbench about the Keep-web service
 
 Workbench has features like "download file from collection" and "show image" which work better if the content is served by Keep-web rather than Workbench itself. We recommend using the two different hostnames ("download" and "collections" above) for file downloads and inline content respectively.
 
-Add the following entry to your Workbench configuration file (@/etc/arvados/workbench/application.yml@). This URL will be used for file downloads.
+Add the following entry to your Workbench cluster configuration file (@/etc/arvados/config.yml@). This URL will be used for file downloads.
 
 <notextile>
-<pre><code>keep_web_download_url: https://download.<span class="userinput">uuid_prefix</span>.your.domain/c=%{uuid_or_pdh}
+<pre><code>Clusters:
+  zzzzz:
+    Services:
+      WebDAVDownload:
+        ExternalURL: "https://download.<span class="userinput">uuid_prefix</span>.your.domain/c=%{uuid_or_pdh}"
 </code></pre>
 </notextile>
 
-Additionally, add *one* of the following entries to your Workbench configuration file, depending on your DNS setup. This URL will be used to serve user content that can be displayed in the browser, like image previews and static HTML pages.
+Additionally, add *one* of the following entries to your Workbench cluster configuration file, depending on your DNS setup. This URL will be used to serve user content that can be displayed in the browser, like image previews and static HTML pages.
 
 <notextile>
-<pre><code>keep_web_url: https://%{uuid_or_pdh}--collections.<span class="userinput">uuid_prefix</span>.your.domain
-keep_web_url: https://%{uuid_or_pdh}.collections.<span class="userinput">uuid_prefix</span>.your.domain
-keep_web_url: https://collections.<span class="userinput">uuid_prefix</span>.your.domain/c=%{uuid_or_pdh}
+<pre><code>Clusters:
+  zzzzz:
+    Services:
+      WebDAV:
+        ExternalURL: "https://%{uuid_or_pdh}--collections.<span class="userinput">uuid_prefix</span>.your.domain"
+        ExternalURL: "https://%{uuid_or_pdh}.collections.<span class="userinput">uuid_prefix</span>.your.domain"
+        ExternalURL: "https://collections.<span class="userinput">uuid_prefix</span>.your.domain/c=%{uuid_or_pdh}"
 </code></pre>
 </notextile>
index 9ac4aeeb9606461dd23f0f743e6a17a065662c76..7fd6306185ed7930d2e64cc1d9387a1f16b19f95 100644 (file)
@@ -212,8 +212,8 @@ Clusters:
       # to run an open instance where anyone can create an account and use
       # the system without requiring manual approval.
       #
-      # The params auto_setup_new_users_with_* are meaningful only when auto_setup_new_users is turned on.
-      # auto_setup_name_blacklist is a list of usernames to be blacklisted for auto setup.
+      # The params AutoSetupNewUsersWith* are meaningful only when AutoSetupNewUsers is turned on.
+      # AutoSetupUsernameBlacklist is a list of usernames to be blacklisted for auto setup.
       AutoSetupNewUsers: false
       AutoSetupNewUsersWithVmUUID: ""
       AutoSetupNewUsersWithRepository: false
@@ -226,7 +226,7 @@ Clusters:
         syslog: {}
         SAMPLE: {}
 
-      # When new_users_are_active is set to true, new users will be active
+      # When NewUsersAreActive is set to true, new users will be active
       # immediately.  This skips the "self-activate" step which enforces
       # user agreements.  Should only be enabled for development.
       NewUsersAreActive: false
@@ -238,7 +238,7 @@ Clusters:
       # should be an address associated with a Google account.
       AutoAdminUserWithEmail: ""
 
-      # If auto_admin_first_user is set to true, the first user to log in when no
+      # If AutoAdminFirstUser is set to true, the first user to log in when no
       # other admin users exist will automatically become an admin user.
       AutoAdminFirstUser: false
 
@@ -256,6 +256,11 @@ Clusters:
       # in the directory where your API server is running.
       AnonymousUserToken: ""
 
+      # Set AnonymousUserToken to enable anonymous user access. You can get
+      # the token by running "bundle exec ./script/get_anonymous_user_token.rb"
+      # in the directory where your API server is running.
+      AnonymousUserToken: ""
+
     AuditLogs:
       # Time to keep audit logs, in seconds. (An audit log is a row added
       # to the "logs" table in the PostgreSQL database each time an
@@ -267,7 +272,7 @@ Clusters:
 
       # Maximum number of log rows to delete in a single SQL transaction.
       #
-      # If max_audit_log_delete_batch is 0, log entries will never be
+      # If MaxDeleteBatch is 0, log entries will never be
       # deleted by Arvados. Cleanup can be done by an external process
       # without affecting any Arvados system processes, as long as very
       # recent (<5 minutes old) logs are not deleted.
@@ -316,7 +321,7 @@ Clusters:
       # identical to the permission key given to Keep. IMPORTANT: This is
       # a site secret. It should be at least 50 characters.
       #
-      # Modifying blob_signing_key will invalidate all existing
+      # Modifying BlobSigningKey will invalidate all existing
       # signatures, which can cause programs to fail (e.g., arv-put,
       # arv-get, and Crunch jobs).  To avoid errors, rotate keys only when
       # no such processes are running.
@@ -338,14 +343,14 @@ Clusters:
       # keepstore servers.  Otherwise, reading data blocks and saving
       # collections will fail with HTTP 403 permission errors.
       #
-      # Modifying blob_signature_ttl invalidates existing signatures; see
-      # blob_signing_key note above.
+      # Modifying BlobSigningTTL invalidates existing signatures; see
+      # BlobSigningKey note above.
       #
       # The default is 2 weeks.
       BlobSigningTTL: 336h
 
       # Default lifetime for ephemeral collections: 2 weeks. This must not
-      # be less than blob_signature_ttl.
+      # be less than BlobSigningTTL.
       DefaultTrashLifetime: 336h
 
       # Interval (seconds) between trash sweeps. During a trash sweep,
@@ -355,7 +360,7 @@ Clusters:
 
       # If true, enable collection versioning.
       # When a collection's preserve_version field is true or the current version
-      # is older than the amount of seconds defined on preserve_version_if_idle,
+      # is older than the amount of seconds defined on PreserveVersionIfIdle,
       # a snapshot of the collection's previous state is created and linked to
       # the current collection.
       CollectionVersioning: false
@@ -392,6 +397,21 @@ Clusters:
       # The default setting (false) is appropriate for a multi-user site.
       TrustAllContent: false
 
+      # Cache parameters for WebDAV content serving:
+      # * TTL: Maximum time to cache manifests and permission checks.
+      # * UUIDTTL: Maximum time to cache collection state.
+      # * MaxCollectionEntries: Maximum number of collection cache entries.
+      # * MaxCollectionBytes: Approximate memory limit for collection cache.
+      # * MaxPermissionEntries: Maximum number of permission cache entries.
+      # * MaxUUIDEntries: Maximum number of UUID cache entries.
+      WebDAVCache:
+        TTL: 300s
+        UUIDTTL: 5s
+        MaxCollectionEntries: 1000
+        MaxCollectionBytes:   100000000
+        MaxPermissionEntries: 1000
+        MaxUUIDEntries:       1000
+
     Login:
       # These settings are provided by your OAuth2 provider (e.g.,
       # sso-provider).
index 12581ddff08123cb3026afa0d7ecf5f510311570..019979d39fe2d068c4a196d398e5111d137c35c1 100644 (file)
@@ -326,3 +326,73 @@ func (ldr *Loader) loadOldWebsocketConfig(cfg *arvados.Config) error {
        cfg.Clusters[cluster.ClusterID] = *cluster
        return nil
 }
+
+const defaultKeepWebConfigPath = "/etc/arvados/keep-web/keep-web.yml"
+
+type oldKeepWebConfig struct {
+       Client *arvados.Client
+
+       Listen string
+
+       AnonymousTokens    []string
+       AttachmentOnlyHost string
+       TrustAllContent    bool
+
+       Cache struct {
+               TTL                  arvados.Duration
+               UUIDTTL              arvados.Duration
+               MaxCollectionEntries int
+               MaxCollectionBytes   int64
+               MaxPermissionEntries int
+               MaxUUIDEntries       int
+       }
+
+       // Hack to support old command line flag, which is a bool
+       // meaning "get actual token from environment".
+       deprecatedAllowAnonymous bool
+
+       // Authorization token to be included in all health check requests.
+       ManagementToken string
+}
+
+func (ldr *Loader) loadOldKeepWebConfig(cfg *arvados.Config) error {
+       if ldr.KeepWebPath == "" {
+               return nil
+       }
+       var oc oldKeepWebConfig
+       err := ldr.loadOldConfigHelper("keep-web", ldr.KeepWebPath, &oc)
+       if os.IsNotExist(err) && ldr.KeepWebPath == defaultKeepWebConfigPath {
+               return nil
+       } else if err != nil {
+               return err
+       }
+
+       cluster, err := cfg.GetCluster("")
+       if err != nil {
+               return err
+       }
+
+       loadOldClientConfig(cluster, oc.Client)
+
+       cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
+       cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
+       cluster.Services.WebDAVDownload.ExternalURL = arvados.URL{Host: oc.AttachmentOnlyHost}
+       cluster.TLS.Insecure = oc.Client.Insecure
+       cluster.ManagementToken = oc.ManagementToken
+       cluster.Collections.TrustAllContent = oc.TrustAllContent
+       cluster.Collections.WebDAVCache.TTL = oc.Cache.TTL
+       cluster.Collections.WebDAVCache.UUIDTTL = oc.Cache.UUIDTTL
+       cluster.Collections.WebDAVCache.MaxCollectionEntries = oc.Cache.MaxCollectionEntries
+       cluster.Collections.WebDAVCache.MaxCollectionBytes = oc.Cache.MaxCollectionBytes
+       cluster.Collections.WebDAVCache.MaxPermissionEntries = oc.Cache.MaxPermissionEntries
+       cluster.Collections.WebDAVCache.MaxUUIDEntries = oc.Cache.MaxUUIDEntries
+       if len(oc.AnonymousTokens) > 0 {
+               cluster.Users.AnonymousUserToken = oc.AnonymousTokens[0]
+               if len(oc.AnonymousTokens) > 1 {
+                       ldr.Logger.Warn("More than 1 anonymous tokens configured, using only the first and discarding the rest.")
+               }
+       }
+
+       cfg.Clusters[cluster.ClusterID] = *cluster
+       return nil
+}
index b125d7dc917db54283ecf98eddf9daade25d29f8..f6b19db252f8292eb4cace79cdf55f88d56f5701 100644 (file)
@@ -89,6 +89,7 @@ var whitelist = map[string]bool{
        "Collections.PreserveVersionIfIdle":            true,
        "Collections.TrashSweepInterval":               false,
        "Collections.TrustAllContent":                  false,
+       "Collections.WebDAVCache":                      false,
        "Containers":                                   true,
        "Containers.CloudVMs":                          false,
        "Containers.CrunchRunCommand":                  false,
index 602f30e1dae5480bb22ed39e3b0a9bf8c1e04e8f..14bbf33dbdfd7142c78f08b90d971d072b090aa4 100644 (file)
@@ -218,8 +218,8 @@ Clusters:
       # to run an open instance where anyone can create an account and use
       # the system without requiring manual approval.
       #
-      # The params auto_setup_new_users_with_* are meaningful only when auto_setup_new_users is turned on.
-      # auto_setup_name_blacklist is a list of usernames to be blacklisted for auto setup.
+      # The params AutoSetupNewUsersWith* are meaningful only when AutoSetupNewUsers is turned on.
+      # AutoSetupUsernameBlacklist is a list of usernames to be blacklisted for auto setup.
       AutoSetupNewUsers: false
       AutoSetupNewUsersWithVmUUID: ""
       AutoSetupNewUsersWithRepository: false
@@ -232,7 +232,7 @@ Clusters:
         syslog: {}
         SAMPLE: {}
 
-      # When new_users_are_active is set to true, new users will be active
+      # When NewUsersAreActive is set to true, new users will be active
       # immediately.  This skips the "self-activate" step which enforces
       # user agreements.  Should only be enabled for development.
       NewUsersAreActive: false
@@ -244,7 +244,7 @@ Clusters:
       # should be an address associated with a Google account.
       AutoAdminUserWithEmail: ""
 
-      # If auto_admin_first_user is set to true, the first user to log in when no
+      # If AutoAdminFirstUser is set to true, the first user to log in when no
       # other admin users exist will automatically become an admin user.
       AutoAdminFirstUser: false
 
@@ -262,6 +262,11 @@ Clusters:
       # in the directory where your API server is running.
       AnonymousUserToken: ""
 
+      # Set AnonymousUserToken to enable anonymous user access. You can get
+      # the token by running "bundle exec ./script/get_anonymous_user_token.rb"
+      # in the directory where your API server is running.
+      AnonymousUserToken: ""
+
     AuditLogs:
       # Time to keep audit logs, in seconds. (An audit log is a row added
       # to the "logs" table in the PostgreSQL database each time an
@@ -273,7 +278,7 @@ Clusters:
 
       # Maximum number of log rows to delete in a single SQL transaction.
       #
-      # If max_audit_log_delete_batch is 0, log entries will never be
+      # If MaxDeleteBatch is 0, log entries will never be
       # deleted by Arvados. Cleanup can be done by an external process
       # without affecting any Arvados system processes, as long as very
       # recent (<5 minutes old) logs are not deleted.
@@ -322,7 +327,7 @@ Clusters:
       # identical to the permission key given to Keep. IMPORTANT: This is
       # a site secret. It should be at least 50 characters.
       #
-      # Modifying blob_signing_key will invalidate all existing
+      # Modifying BlobSigningKey will invalidate all existing
       # signatures, which can cause programs to fail (e.g., arv-put,
       # arv-get, and Crunch jobs).  To avoid errors, rotate keys only when
       # no such processes are running.
@@ -344,14 +349,14 @@ Clusters:
       # keepstore servers.  Otherwise, reading data blocks and saving
       # collections will fail with HTTP 403 permission errors.
       #
-      # Modifying blob_signature_ttl invalidates existing signatures; see
-      # blob_signing_key note above.
+      # Modifying BlobSigningTTL invalidates existing signatures; see
+      # BlobSigningKey note above.
       #
       # The default is 2 weeks.
       BlobSigningTTL: 336h
 
       # Default lifetime for ephemeral collections: 2 weeks. This must not
-      # be less than blob_signature_ttl.
+      # be less than BlobSigningTTL.
       DefaultTrashLifetime: 336h
 
       # Interval (seconds) between trash sweeps. During a trash sweep,
@@ -361,7 +366,7 @@ Clusters:
 
       # If true, enable collection versioning.
       # When a collection's preserve_version field is true or the current version
-      # is older than the amount of seconds defined on preserve_version_if_idle,
+      # is older than the amount of seconds defined on PreserveVersionIfIdle,
       # a snapshot of the collection's previous state is created and linked to
       # the current collection.
       CollectionVersioning: false
@@ -398,6 +403,21 @@ Clusters:
       # The default setting (false) is appropriate for a multi-user site.
       TrustAllContent: false
 
+      # Cache parameters for WebDAV content serving:
+      # * TTL: Maximum time to cache manifests and permission checks.
+      # * UUIDTTL: Maximum time to cache collection state.
+      # * MaxCollectionEntries: Maximum number of collection cache entries.
+      # * MaxCollectionBytes: Approximate memory limit for collection cache.
+      # * MaxPermissionEntries: Maximum number of permission cache entries.
+      # * MaxUUIDEntries: Maximum number of UUID cache entries.
+      WebDAVCache:
+        TTL: 300s
+        UUIDTTL: 5s
+        MaxCollectionEntries: 1000
+        MaxCollectionBytes:   100000000
+        MaxPermissionEntries: 1000
+        MaxUUIDEntries:       1000
+
     Login:
       # These settings are provided by your OAuth2 provider (e.g.,
       # sso-provider).
index 33d31f71c9172272cf901db29cc202c9338dd0bd..58747829b55a101545706c0e652e18d1f3c02c2f 100644 (file)
@@ -31,6 +31,7 @@ type Loader struct {
 
        Path                    string
        KeepstorePath           string
+       KeepWebPath             string
        CrunchDispatchSlurmPath string
        WebsocketPath           string
 
@@ -60,6 +61,7 @@ func NewLoader(stdin io.Reader, logger logrus.FieldLogger) *Loader {
 func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
        flagset.StringVar(&ldr.Path, "config", arvados.DefaultConfigFile, "Site configuration `file` (default may be overridden by setting an ARVADOS_CONFIG environment variable)")
        flagset.StringVar(&ldr.KeepstorePath, "legacy-keepstore-config", defaultKeepstoreConfigPath, "Legacy keepstore configuration `file`")
+       flagset.StringVar(&ldr.KeepWebPath, "legacy-keepweb-config", defaultKeepWebConfigPath, "Legacy keep-web configuration `file`")
        flagset.StringVar(&ldr.CrunchDispatchSlurmPath, "legacy-crunch-dispatch-slurm-config", defaultCrunchDispatchSlurmConfigPath, "Legacy crunch-dispatch-slurm configuration `file`")
        flagset.StringVar(&ldr.WebsocketPath, "legacy-ws-config", defaultWebsocketConfigPath, "Legacy arvados-ws configuration `file`")
        flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
@@ -149,6 +151,12 @@ func (ldr *Loader) loadBytes(path string) ([]byte, error) {
        return ioutil.ReadAll(f)
 }
 
+func (ldr *Loader) LoadDefaults() (*arvados.Config, error) {
+       ldr.configdata = []byte(`Clusters: {zzzzz: {}}`)
+       defer func() { ldr.configdata = nil }()
+       return ldr.Load()
+}
+
 func (ldr *Loader) Load() (*arvados.Config, error) {
        if ldr.configdata == nil {
                buf, err := ldr.loadBytes(ldr.Path)
@@ -230,6 +238,7 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
                // legacy config file for the current component
                for _, err := range []error{
                        ldr.loadOldKeepstoreConfig(&cfg),
+                       ldr.loadOldKeepWebConfig(&cfg),
                        ldr.loadOldCrunchDispatchSlurmConfig(&cfg),
                        ldr.loadOldWebsocketConfig(&cfg),
                } {
index f6b736d587c893bc8128875e5889ebfe0ad78be7..a5cf25b8c9217689b6e4ebfe7926c043a0213612 100644 (file)
@@ -57,6 +57,14 @@ func (sc *Config) GetCluster(clusterID string) (*Cluster, error) {
        }
 }
 
+type WebDAVCacheConfig struct {
+       TTL                  Duration
+       UUIDTTL              Duration
+       MaxCollectionEntries int
+       MaxCollectionBytes   int64
+       MaxPermissionEntries int
+       MaxUUIDEntries       int
+}
 type Cluster struct {
        ClusterID       string `json:"-"`
        ManagementToken string
@@ -100,6 +108,8 @@ type Cluster struct {
                PreserveVersionIfIdle Duration
                TrashSweepInterval    Duration
                TrustAllContent       bool
+
+               WebDAVCache WebDAVCacheConfig
        }
        Git struct {
                Repositories string
index 0f8f1c5f818e2768168e4c71094a67c34fb4478e..679f04b981ec1236871925f168c3a94841f2321f 100644 (file)
@@ -604,14 +604,9 @@ def run_keep_web():
 
     keepwebport = internal_port_from_config("WebDAV")
     env = os.environ.copy()
-    env['ARVADOS_API_TOKEN'] = auth_token('anonymous')
     logf = open(_logfilename('keep-web'), 'a')
     keepweb = subprocess.Popen(
-        ['keep-web',
-         '-allow-anonymous',
-         '-attachment-only-host=download',
-         '-management-token=e687950a23c3a9bceec28c6223a06c79',
-         '-listen=:'+str(keepwebport)],
+        ['keep-web'],
         env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
     with open(_pidfile('keep-web'), 'w') as f:
         f.write(str(keepweb.pid))
@@ -745,7 +740,10 @@ def setup_config():
                 "TLS": {
                     "Insecure": True
                 },
-                "Services": services
+                "Services": services,
+                "Users": {
+                    "AnonymousUserToken": auth_token('anonymous')
+                }
             }
         }
     }
index b9a1f3069f9d3e8bd03563c6785cb0b00d388582..8d1062825e85d79a1fe7e60289437c2182060b62 100644 (file)
@@ -17,13 +17,7 @@ import (
 const metricsUpdateInterval = time.Second / 10
 
 type cache struct {
-       TTL                  arvados.Duration
-       UUIDTTL              arvados.Duration
-       MaxCollectionEntries int
-       MaxCollectionBytes   int64
-       MaxPermissionEntries int
-       MaxUUIDEntries       int
-
+       config      *arvados.WebDAVCacheConfig
        registry    *prometheus.Registry
        metrics     cacheMetrics
        pdhs        *lru.TwoQueueCache
@@ -110,15 +104,15 @@ type cachedPermission struct {
 
 func (c *cache) setup() {
        var err error
-       c.pdhs, err = lru.New2Q(c.MaxUUIDEntries)
+       c.pdhs, err = lru.New2Q(c.config.MaxUUIDEntries)
        if err != nil {
                panic(err)
        }
-       c.collections, err = lru.New2Q(c.MaxCollectionEntries)
+       c.collections, err = lru.New2Q(c.config.MaxCollectionEntries)
        if err != nil {
                panic(err)
        }
-       c.permissions, err = lru.New2Q(c.MaxPermissionEntries)
+       c.permissions, err = lru.New2Q(c.config.MaxPermissionEntries)
        if err != nil {
                panic(err)
        }
@@ -164,7 +158,7 @@ func (c *cache) Update(client *arvados.Client, coll arvados.Collection, fs arvad
        })
        if err == nil {
                c.collections.Add(client.AuthToken+"\000"+coll.PortableDataHash, &cachedCollection{
-                       expire:     time.Now().Add(time.Duration(c.TTL)),
+                       expire:     time.Now().Add(time.Duration(c.config.TTL)),
                        collection: &updated,
                })
        }
@@ -221,11 +215,11 @@ func (c *cache) Get(arv *arvadosclient.ArvadosClient, targetID string, forceRelo
                }
                if current.PortableDataHash == pdh {
                        c.permissions.Add(permKey, &cachedPermission{
-                               expire: time.Now().Add(time.Duration(c.TTL)),
+                               expire: time.Now().Add(time.Duration(c.config.TTL)),
                        })
                        if pdh != targetID {
                                c.pdhs.Add(targetID, &cachedPDH{
-                                       expire: time.Now().Add(time.Duration(c.UUIDTTL)),
+                                       expire: time.Now().Add(time.Duration(c.config.UUIDTTL)),
                                        pdh:    pdh,
                                })
                        }
@@ -246,19 +240,19 @@ func (c *cache) Get(arv *arvadosclient.ArvadosClient, targetID string, forceRelo
        if err != nil {
                return nil, err
        }
-       exp := time.Now().Add(time.Duration(c.TTL))
+       exp := time.Now().Add(time.Duration(c.config.TTL))
        c.permissions.Add(permKey, &cachedPermission{
                expire: exp,
        })
        c.pdhs.Add(targetID, &cachedPDH{
-               expire: time.Now().Add(time.Duration(c.UUIDTTL)),
+               expire: time.Now().Add(time.Duration(c.config.UUIDTTL)),
                pdh:    collection.PortableDataHash,
        })
        c.collections.Add(arv.ApiToken+"\000"+collection.PortableDataHash, &cachedCollection{
                expire:     exp,
                collection: collection,
        })
-       if int64(len(collection.ManifestText)) > c.MaxCollectionBytes/int64(c.MaxCollectionEntries) {
+       if int64(len(collection.ManifestText)) > c.config.MaxCollectionBytes/int64(c.config.MaxCollectionEntries) {
                go c.pruneCollections()
        }
        return collection, nil
@@ -295,7 +289,7 @@ func (c *cache) pruneCollections() {
                }
        }
        for i, k := range keys {
-               if size <= c.MaxCollectionBytes {
+               if size <= c.config.MaxCollectionBytes {
                        break
                }
                if expired[i] {
index d6dd389278e7ae4f05faab2450680a2112fb1545..2a08ea3b402f112cee91156b3a08621ab056ee78 100644 (file)
@@ -33,7 +33,7 @@ func (s *UnitSuite) TestCache(c *check.C) {
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, check.Equals, nil)
 
-       cache := DefaultConfig().Cache
+       cache := DefaultConfig(s.Config).Cache
        cache.registry = prometheus.NewRegistry()
 
        // Hit the same collection 5 times using the same token. Only
@@ -114,7 +114,7 @@ func (s *UnitSuite) TestCacheForceReloadByPDH(c *check.C) {
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, check.Equals, nil)
 
-       cache := DefaultConfig().Cache
+       cache := DefaultConfig(s.Config).Cache
        cache.registry = prometheus.NewRegistry()
 
        for _, forceReload := range []bool{false, true, false, true} {
@@ -134,7 +134,7 @@ func (s *UnitSuite) TestCacheForceReloadByUUID(c *check.C) {
        arv, err := arvadosclient.MakeArvadosClient()
        c.Assert(err, check.Equals, nil)
 
-       cache := DefaultConfig().Cache
+       cache := DefaultConfig(s.Config).Cache
        cache.registry = prometheus.NewRegistry()
 
        for _, forceReload := range []bool{false, true, false, true} {
index 9d9e314fcaf7e25710f1fdd341ca13c7491413f0..f3f8309d329c3150078448cf9535a7f2beac077e 100644 (file)
@@ -52,7 +52,7 @@ func (s *IntegrationSuite) TestCadaverUserProject(c *check.C) {
 }
 
 func (s *IntegrationSuite) testCadaver(c *check.C, password string, pathFunc func(arvados.Collection) (string, string, string), skip func(string) bool) {
-       s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
+       s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
 
        testdata := []byte("the human tragedy consists in the necessity of living with the consequences of actions performed under the pressure of compulsions we do not understand")
 
@@ -340,7 +340,7 @@ func (s *IntegrationSuite) runCadaver(c *check.C, password, path, stdin string)
                // unauthenticated request, which it only does in
                // AttachmentOnlyHost, TrustAllContent, and
                // per-collection vhost cases.
-               s.testServer.Config.AttachmentOnlyHost = s.testServer.Addr
+               s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = s.testServer.Addr
 
                cmd.Env = append(os.Environ(), "HOME="+tempdir)
                f, err := os.OpenFile(filepath.Join(tempdir, ".netrc"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
index d65156f98781f99cd3fbc4a20b2f0ba144ea8f97..8682eac2dd08b5aaa8f308330ca4a2eba06cf34e 100644 (file)
 //
 // Configuration
 //
-// The default configuration file location is
-// /etc/arvados/keep-web/keep-web.yml.
+// The default cluster configuration file location is
+// /etc/arvados/config.yml.
 //
 // Example configuration file
 //
-//     Client:
-//       APIHost: "zzzzz.arvadosapi.com:443"
-//       AuthToken: ""
-//       Insecure: false
-//     Listen: :1234
-//     AnonymousTokens:
-//       - xxxxxxxxxxxxxxxxxxxx
-//     AttachmentOnlyHost: ""
-//     TrustAllContent: false
+//   Clusters:
+//     zzzzz:
+//       SystemRootToken: ""
+//       Services:
+//         Controller:
+//           ExternalURL: "https://example.com"
+//           Insecure: false
+//         WebDAV:
+//           InternalURLs:
+//             "http://:1234/": {}
+//         WebDAVDownload:
+//           InternalURLs:
+//             "http://:1234/": {}
+//           ExternalURL: "https://download.example.com/"
+//       Users:
+//         AnonymousUserToken: "xxxxxxxxxxxxxxxxxxxx"
+//       Collections:
+//         TrustAllContent: false
 //
 // Starting the server
 //
 // Start a server using the default config file
-// /etc/arvados/keep-web/keep-web.yml:
+// /etc/arvados/config.yml:
 //
 //   keep-web
 //
-// Start a server using the config file /path/to/keep-web.yml:
+// Start a server using the config file /path/to/config.yml:
 //
-//   keep-web -config /path/to/keep-web.yml
+//   keep-web -config /path/to/config.yml
 //
 // Proxy configuration
 //
 //
 // Anonymous downloads
 //
-// The "AnonymousTokens" configuration entry is an array of tokens to
-// use when processing anonymous requests, i.e., whenever a web client
+// The "Users.AnonymousUserToken" configuration entry used when
+// when processing anonymous requests, i.e., whenever a web client
 // does not supply its own Arvados API token via path, query string,
 // cookie, or request header.
 //
-//   "AnonymousTokens":["xxxxxxxxxxxxxxxxxxxxxxx"]
+//   Clusters:
+//     zzzzz:
+//       Users:
+//         AnonymousUserToken: "xxxxxxxxxxxxxxxxxxxxxxx"
 //
 // See http://doc.arvados.org/install/install-keep-web.html for examples.
 //
 // only when the designated origin matches exactly the Host header
 // provided by the client or downstream proxy.
 //
-//   "AttachmentOnlyHost":"domain.example:9999"
+//   Clusters:
+//     zzzzz:
+//       Services:
+//         WebDAVDownload:
+//           ExternalURL: "https://domain.example:9999"
 //
 // Trust All Content mode
 //
 //
 // In such cases you can enable trust-all-content mode.
 //
-//   "TrustAllContent":true
+//   Clusters:
+//     zzzzz:
+//       Collections:
+//         TrustAllContent: true
 //
 // When TrustAllContent is enabled, the only effect of the
-// AttachmentOnlyHost flag is to add a "Content-Disposition:
+// Attachment-Only host setting is to add a "Content-Disposition:
 // attachment" header.
 //
-//   "AttachmentOnlyHost":"domain.example:9999",
-//   "TrustAllContent":true
+//   Clusters:
+//     zzzzz:
+//       Services:
+//         WebDAVDownload:
+//           ExternalURL: "https://domain.example:9999"
+//       Collections:
+//         TrustAllContent: true
 //
 // Depending on your site configuration, you might also want to enable
 // the "trust all content" setting in Workbench. Normally, Workbench
index 837579fe25acfbff5283b28bbb7f4375a3322280..863b91a7e1beecae13635cb0e89c830bb264faac 100644 (file)
@@ -81,7 +81,7 @@ func (h *handler) setup() {
        keepclient.RefreshServiceDiscoveryOnSIGHUP()
 
        h.healthHandler = &health.Handler{
-               Token:  h.Config.ManagementToken,
+               Token:  h.Config.cluster.ManagementToken,
                Prefix: "/_health/",
        }
 
@@ -249,9 +249,9 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        var pathToken bool
        var attachment bool
        var useSiteFS bool
-       credentialsOK := h.Config.TrustAllContent
+       credentialsOK := h.Config.cluster.Collections.TrustAllContent
 
-       if r.Host != "" && r.Host == h.Config.AttachmentOnlyHost {
+       if r.Host != "" && r.Host == h.Config.cluster.Services.WebDAVDownload.ExternalURL.Host {
                credentialsOK = true
                attachment = true
        } else if r.FormValue("disposition") == "attachment" {
@@ -351,7 +351,7 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        }
 
        if tokens == nil {
-               tokens = append(reqTokens, h.Config.AnonymousTokens...)
+               tokens = append(reqTokens, h.Config.cluster.Users.AnonymousUserToken)
        }
 
        if len(targetPath) > 0 && targetPath[0] == "_" {
index dd91df354900175592501ce794a6d9dc46cf8f41..fe8e767c4c63aa9d51d390220b7c5fe14173c63e 100644 (file)
@@ -17,6 +17,7 @@ import (
        "regexp"
        "strings"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/auth"
@@ -25,10 +26,19 @@ import (
 
 var _ = check.Suite(&UnitSuite{})
 
-type UnitSuite struct{}
+type UnitSuite struct {
+       Config *arvados.Config
+}
+
+func (s *UnitSuite) SetUpTest(c *check.C) {
+       ldr := config.NewLoader(nil, nil)
+       cfg, err := ldr.LoadDefaults()
+       c.Assert(err, check.IsNil)
+       s.Config = cfg
+}
 
 func (s *UnitSuite) TestCORSPreflight(c *check.C) {
-       h := handler{Config: DefaultConfig()}
+       h := handler{Config: DefaultConfig(s.Config)}
        u := mustParseURL("http://keep-web.example/c=" + arvadostest.FooCollection + "/foo")
        req := &http.Request{
                Method:     "OPTIONS",
@@ -78,8 +88,8 @@ func (s *UnitSuite) TestInvalidUUID(c *check.C) {
                        RequestURI: u.RequestURI(),
                }
                resp := httptest.NewRecorder()
-               cfg := DefaultConfig()
-               cfg.AnonymousTokens = []string{arvadostest.AnonymousToken}
+               cfg := DefaultConfig(s.Config)
+               cfg.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
                h := handler{Config: cfg}
                h.ServeHTTP(resp, req)
                c.Check(resp.Code, check.Equals, http.StatusNotFound)
@@ -338,7 +348,7 @@ func (s *IntegrationSuite) TestVhostRedirectQueryTokenRequestAttachment(c *check
 }
 
 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSiteFS(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = "download.example.com"
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
        resp := s.testVhostRedirectTokenToCookie(c, "GET",
                "download.example.com/by_id/"+arvadostest.FooCollection+"/foo",
                "?api_token="+arvadostest.ActiveToken,
@@ -351,7 +361,7 @@ func (s *IntegrationSuite) TestVhostRedirectQueryTokenSiteFS(c *check.C) {
 }
 
 func (s *IntegrationSuite) TestPastCollectionVersionFileAccess(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = "download.example.com"
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
        resp := s.testVhostRedirectTokenToCookie(c, "GET",
                "download.example.com/c="+arvadostest.WazVersion1Collection+"/waz",
                "?api_token="+arvadostest.ActiveToken,
@@ -373,7 +383,7 @@ func (s *IntegrationSuite) TestPastCollectionVersionFileAccess(c *check.C) {
 }
 
 func (s *IntegrationSuite) TestVhostRedirectQueryTokenTrustAllContent(c *check.C) {
-       s.testServer.Config.TrustAllContent = true
+       s.testServer.Config.cluster.Collections.TrustAllContent = true
        s.testVhostRedirectTokenToCookie(c, "GET",
                "example.com/c="+arvadostest.FooCollection+"/foo",
                "?api_token="+arvadostest.ActiveToken,
@@ -385,7 +395,7 @@ func (s *IntegrationSuite) TestVhostRedirectQueryTokenTrustAllContent(c *check.C
 }
 
 func (s *IntegrationSuite) TestVhostRedirectQueryTokenAttachmentOnlyHost(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = "example.com:1234"
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com:1234"
 
        s.testVhostRedirectTokenToCookie(c, "GET",
                "example.com/c="+arvadostest.FooCollection+"/foo",
@@ -430,7 +440,7 @@ func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie404(c *check.C)
 }
 
 func (s *IntegrationSuite) TestAnonymousTokenOK(c *check.C) {
-       s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
+       s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
        s.testVhostRedirectTokenToCookie(c, "GET",
                "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
                "",
@@ -442,7 +452,7 @@ func (s *IntegrationSuite) TestAnonymousTokenOK(c *check.C) {
 }
 
 func (s *IntegrationSuite) TestAnonymousTokenError(c *check.C) {
-       s.testServer.Config.AnonymousTokens = []string{"anonymousTokenConfiguredButInvalid"}
+       s.testServer.Config.cluster.Users.AnonymousUserToken = "anonymousTokenConfiguredButInvalid"
        s.testVhostRedirectTokenToCookie(c, "GET",
                "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
                "",
@@ -454,7 +464,7 @@ func (s *IntegrationSuite) TestAnonymousTokenError(c *check.C) {
 }
 
 func (s *IntegrationSuite) TestSpecialCharsInPath(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = "download.example.com"
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
 
        client := s.testServer.Config.Client
        client.AuthToken = arvadostest.ActiveToken
@@ -560,17 +570,17 @@ func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, ho
 }
 
 func (s *IntegrationSuite) TestDirectoryListingWithAnonymousToken(c *check.C) {
-       s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
+       s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
        s.testDirectoryListing(c)
 }
 
 func (s *IntegrationSuite) TestDirectoryListingWithNoAnonymousToken(c *check.C) {
-       s.testServer.Config.AnonymousTokens = nil
+       s.testServer.Config.cluster.Users.AnonymousUserToken = ""
        s.testDirectoryListing(c)
 }
 
 func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = "download.example.com"
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "download.example.com"
        authHeader := http.Header{
                "Authorization": {"OAuth2 " + arvadostest.ActiveToken},
        }
@@ -707,7 +717,7 @@ func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
                        cutDirs: 2,
                },
        } {
-               c.Logf("HTML: %q => %q", trial.uri, trial.expect)
+               comment := check.Commentf("HTML: %q => %q", trial.uri, trial.expect)
                resp := httptest.NewRecorder()
                u := mustParseURL("//" + trial.uri)
                req := &http.Request{
@@ -736,19 +746,19 @@ func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
                        s.testServer.Handler.ServeHTTP(resp, req)
                }
                if trial.redirect != "" {
-                       c.Check(req.URL.Path, check.Equals, trial.redirect)
+                       c.Check(req.URL.Path, check.Equals, trial.redirect, comment)
                }
                if trial.expect == nil {
-                       c.Check(resp.Code, check.Equals, http.StatusNotFound)
+                       c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
                } else {
-                       c.Check(resp.Code, check.Equals, http.StatusOK)
+                       c.Check(resp.Code, check.Equals, http.StatusOK, comment)
                        for _, e := range trial.expect {
-                               c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./`+e+`".*`)
+                               c.Check(resp.Body.String(), check.Matches, `(?ms).*href="./`+e+`".*`, comment)
                        }
-                       c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`)
+                       c.Check(resp.Body.String(), check.Matches, `(?ms).*--cut-dirs=`+fmt.Sprintf("%d", trial.cutDirs)+` .*`, comment)
                }
 
-               c.Logf("WebDAV: %q => %q", trial.uri, trial.expect)
+               comment = check.Commentf("WebDAV: %q => %q", trial.uri, trial.expect)
                req = &http.Request{
                        Method:     "OPTIONS",
                        Host:       u.Host,
@@ -760,9 +770,9 @@ func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
                resp = httptest.NewRecorder()
                s.testServer.Handler.ServeHTTP(resp, req)
                if trial.expect == nil {
-                       c.Check(resp.Code, check.Equals, http.StatusNotFound)
+                       c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
                } else {
-                       c.Check(resp.Code, check.Equals, http.StatusOK)
+                       c.Check(resp.Code, check.Equals, http.StatusOK, comment)
                }
 
                req = &http.Request{
@@ -776,11 +786,11 @@ func (s *IntegrationSuite) testDirectoryListing(c *check.C) {
                resp = httptest.NewRecorder()
                s.testServer.Handler.ServeHTTP(resp, req)
                if trial.expect == nil {
-                       c.Check(resp.Code, check.Equals, http.StatusNotFound)
+                       c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
                } else {
-                       c.Check(resp.Code, check.Equals, http.StatusMultiStatus)
+                       c.Check(resp.Code, check.Equals, http.StatusMultiStatus, comment)
                        for _, e := range trial.expect {
-                               c.Check(resp.Body.String(), check.Matches, `(?ms).*<D:href>`+filepath.Join(u.Path, e)+`</D:href>.*`)
+                               c.Check(resp.Body.String(), check.Matches, `(?ms).*<D:href>`+filepath.Join(u.Path, e)+`</D:href>.*`, comment)
                        }
                }
        }
@@ -802,7 +812,7 @@ func (s *IntegrationSuite) TestDeleteLastFile(c *check.C) {
 
        var updated arvados.Collection
        for _, fnm := range []string{"foo.txt", "bar.txt"} {
-               s.testServer.Config.AttachmentOnlyHost = "example.com"
+               s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = "example.com"
                u, _ := url.Parse("http://example.com/c=" + newCollection.UUID + "/" + fnm)
                req := &http.Request{
                        Method:     "DELETE",
@@ -827,7 +837,7 @@ func (s *IntegrationSuite) TestDeleteLastFile(c *check.C) {
 }
 
 func (s *IntegrationSuite) TestHealthCheckPing(c *check.C) {
-       s.testServer.Config.ManagementToken = arvadostest.ManagementToken
+       s.testServer.Config.cluster.ManagementToken = arvadostest.ManagementToken
        authHeader := http.Header{
                "Authorization": {"Bearer " + arvadostest.ManagementToken},
        }
index 018b5a2e817a19075fa5220ef8c87d1541608ed5..9dae50a8a56b3dbc20ef3fa6709499a197d41b64 100644 (file)
@@ -8,52 +8,36 @@ import (
        "flag"
        "fmt"
        "os"
-       "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/config"
        "github.com/coreos/go-systemd/daemon"
        log "github.com/sirupsen/logrus"
+       yaml "gopkg.in/yaml.v2"
 )
 
 var (
-       defaultConfigPath = "/etc/arvados/keep-web/keep-web.yml"
-       version           = "dev"
+       version = "dev"
 )
 
 // Config specifies server configuration.
 type Config struct {
-       Client arvados.Client
-
-       Listen string
-
-       AnonymousTokens    []string
-       AttachmentOnlyHost string
-       TrustAllContent    bool
-
-       Cache cache
-
-       // Hack to support old command line flag, which is a bool
-       // meaning "get actual token from environment".
-       deprecatedAllowAnonymous bool
-
-       //Authorization token to be included in all health check requests.
-       ManagementToken string
+       Client  arvados.Client
+       Cache   cache
+       cluster *arvados.Cluster
 }
 
 // DefaultConfig returns the default configuration.
-func DefaultConfig() *Config {
-       return &Config{
-               Listen: ":80",
-               Cache: cache{
-                       TTL:                  arvados.Duration(5 * time.Minute),
-                       UUIDTTL:              arvados.Duration(5 * time.Second),
-                       MaxCollectionEntries: 1000,
-                       MaxCollectionBytes:   100000000,
-                       MaxPermissionEntries: 1000,
-                       MaxUUIDEntries:       1000,
-               },
+func DefaultConfig(arvCfg *arvados.Config) *Config {
+       cfg := Config{}
+       var cls *arvados.Cluster
+       var err error
+       if cls, err = arvCfg.GetCluster(""); err != nil {
+               log.Fatal(err)
        }
+       cfg.cluster = cls
+       cfg.Cache.config = &cfg.cluster.Collections.WebDAVCache
+       return &cfg
 }
 
 func init() {
@@ -71,57 +55,57 @@ func init() {
        })
 }
 
-func main() {
-       cfg := DefaultConfig()
-
-       var configPath string
-       deprecated := " (DEPRECATED -- use config file instead)"
-       flag.StringVar(&configPath, "config", defaultConfigPath,
-               "`path` to JSON or YAML configuration file")
-       flag.StringVar(&cfg.Listen, "listen", "",
-               "address:port or :port to listen on"+deprecated)
-       flag.BoolVar(&cfg.deprecatedAllowAnonymous, "allow-anonymous", false,
-               "Load an anonymous token from the ARVADOS_API_TOKEN environment variable"+deprecated)
-       flag.StringVar(&cfg.AttachmentOnlyHost, "attachment-only-host", "",
-               "Only serve attachments at the given `host:port`"+deprecated)
-       flag.BoolVar(&cfg.TrustAllContent, "trust-all-content", false,
-               "Serve non-public content from a single origin. Dangerous: read docs before using!"+deprecated)
-       flag.StringVar(&cfg.ManagementToken, "management-token", "",
-               "Authorization token to be included in all health check requests.")
-
-       dumpConfig := flag.Bool("dump-config", false,
+func configure(logger log.FieldLogger, args []string) *Config {
+       flags := flag.NewFlagSet(args[0], flag.ExitOnError)
+
+       loader := config.NewLoader(os.Stdin, logger)
+       loader.SetupFlags(flags)
+
+       dumpConfig := flags.Bool("dump-config", false,
                "write current configuration to stdout and exit")
-       getVersion := flag.Bool("version", false,
+       getVersion := flags.Bool("version", false,
                "print version information and exit.")
-       flag.Usage = usage
-       flag.Parse()
+
+       args = loader.MungeLegacyConfigArgs(logger, args[1:], "-legacy-keepweb-config")
+       flags.Parse(args)
 
        // Print version information if requested
        if *getVersion {
                fmt.Printf("keep-web %s\n", version)
-               return
+               return nil
        }
 
-       if err := config.LoadFile(cfg, configPath); err != nil {
-               if h := os.Getenv("ARVADOS_API_HOST"); h != "" && configPath == defaultConfigPath {
-                       log.Printf("DEPRECATED: Using ARVADOS_API_HOST environment variable. Use config file instead.")
-                       cfg.Client.APIHost = h
-               } else {
+       arvCfg, err := loader.Load()
+       if err != nil {
+               log.Fatal(err)
+       }
+       cfg := DefaultConfig(arvCfg)
+
+       if *dumpConfig {
+               out, err := yaml.Marshal(cfg)
+               if err != nil {
                        log.Fatal(err)
                }
+               _, err = os.Stdout.Write(out)
+               if err != nil {
+                       log.Fatal(err)
+               }
+               return nil
        }
-       if cfg.deprecatedAllowAnonymous {
-               log.Printf("DEPRECATED: Using -allow-anonymous command line flag with ARVADOS_API_TOKEN environment variable. Use config file instead.")
-               cfg.AnonymousTokens = []string{os.Getenv("ARVADOS_API_TOKEN")}
-       }
+       return cfg
+}
 
-       if *dumpConfig {
-               log.Fatal(config.DumpAndExit(cfg))
+func main() {
+       logger := log.New()
+
+       cfg := configure(logger, os.Args)
+       if cfg == nil {
+               return
        }
 
        log.Printf("keep-web %s started", version)
 
-       os.Setenv("ARVADOS_API_HOST", cfg.Client.APIHost)
+       os.Setenv("ARVADOS_API_HOST", cfg.cluster.Services.Controller.ExternalURL.Host)
        srv := &server{Config: cfg}
        if err := srv.Start(); err != nil {
                log.Fatal(err)
index 167fbbe5b85cf93f012d072e1fd97af3f5bd7106..28a9dde2471b2cab9fef1293e308546b655504e9 100644 (file)
@@ -8,6 +8,7 @@ import (
        "context"
        "net/http"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
        "github.com/prometheus/client_golang/prometheus"
@@ -25,8 +26,12 @@ func (srv *server) Start() error {
        h.Config.Cache.registry = reg
        ctx := ctxlog.Context(context.Background(), logrus.StandardLogger())
        mh := httpserver.Instrument(reg, nil, httpserver.HandlerWithContext(ctx, httpserver.AddRequestIDs(httpserver.LogRequests(h))))
-       h.MetricsAPI = mh.ServeAPI(h.Config.ManagementToken, http.NotFoundHandler())
+       h.MetricsAPI = mh.ServeAPI(h.Config.cluster.ManagementToken, http.NotFoundHandler())
        srv.Handler = mh
-       srv.Addr = srv.Config.Listen
+       var listen arvados.URL
+       for listen = range srv.Config.cluster.Services.WebDAV.InternalURLs {
+               break
+       }
+       srv.Addr = listen.Host
        return srv.Server.Start()
 }
index 0263dcf08f92c906032664c8b0d3b6de8726d9b7..12596b16bb1bd3edce95bc8daf63dac100819e15 100644 (file)
@@ -16,11 +16,14 @@ import (
        "os/exec"
        "strings"
        "testing"
+       "time"
 
+       "git.curoverse.com/arvados.git/lib/config"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/keepclient"
+       log "github.com/sirupsen/logrus"
        check "gopkg.in/check.v1"
 )
 
@@ -148,7 +151,7 @@ type curlCase struct {
 }
 
 func (s *IntegrationSuite) Test200(c *check.C) {
-       s.testServer.Config.AnonymousTokens = []string{arvadostest.AnonymousToken}
+       s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
        for _, spec := range []curlCase{
                // My collection
                {
@@ -298,7 +301,7 @@ func (s *IntegrationSuite) runCurl(c *check.C, token, host, uri string, args ...
 }
 
 func (s *IntegrationSuite) TestMetrics(c *check.C) {
-       s.testServer.Config.AttachmentOnlyHost = s.testServer.Addr
+       s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = s.testServer.Addr
        origin := "http://" + s.testServer.Addr
        req, _ := http.NewRequest("GET", origin+"/notfound", nil)
        _, err := http.DefaultClient.Do(req)
@@ -420,6 +423,66 @@ func (s *IntegrationSuite) SetUpSuite(c *check.C) {
        kc.PutB([]byte("waz"))
 }
 
+func (s *UnitSuite) TestLegacyConfig(c *check.C) {
+       content := []byte(`
+{
+       "Client": {
+               "Scheme": "",
+               "APIHost": "example.com",
+               "AuthToken": "abcdefg",
+       },
+       "Listen": ":80",
+       "AnonymousTokens": [
+               "anonusertoken"
+       ],
+       "AttachmentOnlyHost": "download.example.com",
+       "TrustAllContent": true,
+       "Cache": {
+               "TTL": "1m",
+               "UUIDTTL": "1s",
+               "MaxCollectionEntries": 42,
+               "MaxCollectionBytes": 1234567890,
+               "MaxPermissionEntries": 100,
+               "MaxUUIDEntries": 100
+       },
+       "ManagementToken": "xyzzy"
+}
+`)
+       tmpfile, err := ioutil.TempFile("", "example")
+       if err != nil {
+               c.Error(err)
+       }
+       defer os.Remove(tmpfile.Name())
+
+       if _, err := tmpfile.Write(content); err != nil {
+               c.Error(err)
+       }
+       if err := tmpfile.Close(); err != nil {
+               c.Error(err)
+       }
+       cfg := configure(log.New(), []string{"keep-web", "-config", tmpfile.Name()})
+       c.Check(cfg, check.NotNil)
+       c.Check(cfg.cluster, check.NotNil)
+
+       c.Check(cfg.cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
+       c.Check(cfg.cluster.SystemRootToken, check.Equals, "abcdefg")
+
+       c.Check(cfg.cluster.Collections.WebDAVCache.TTL, check.Equals, arvados.Duration(60*time.Second))
+       c.Check(cfg.cluster.Collections.WebDAVCache.UUIDTTL, check.Equals, arvados.Duration(time.Second))
+       c.Check(cfg.cluster.Collections.WebDAVCache.MaxCollectionEntries, check.Equals, 42)
+       c.Check(cfg.cluster.Collections.WebDAVCache.MaxCollectionBytes, check.Equals, int64(1234567890))
+       c.Check(cfg.cluster.Collections.WebDAVCache.MaxPermissionEntries, check.Equals, 100)
+       c.Check(cfg.cluster.Collections.WebDAVCache.MaxUUIDEntries, check.Equals, 100)
+
+       c.Check(cfg.cluster.Services.WebDAVDownload.ExternalURL, check.Equals, arvados.URL{Host: "download.example.com"})
+       c.Check(cfg.cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
+       c.Check(cfg.cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
+
+       c.Check(cfg.cluster.Collections.TrustAllContent, check.Equals, true)
+       c.Check(cfg.cluster.Users.AnonymousUserToken, check.Equals, "anonusertoken")
+       c.Check(cfg.cluster.ManagementToken, check.Equals, "xyzzy")
+}
+
 func (s *IntegrationSuite) TearDownSuite(c *check.C) {
        arvadostest.StopKeep(2)
        arvadostest.StopAPI()
@@ -427,15 +490,21 @@ func (s *IntegrationSuite) TearDownSuite(c *check.C) {
 
 func (s *IntegrationSuite) SetUpTest(c *check.C) {
        arvadostest.ResetEnv()
-       cfg := DefaultConfig()
+       ldr := config.NewLoader(nil, nil)
+       arvCfg, err := ldr.LoadDefaults()
+       cfg := DefaultConfig(arvCfg)
+       c.Assert(err, check.IsNil)
        cfg.Client = arvados.Client{
                APIHost:  testAPIHost,
                Insecure: true,
        }
-       cfg.Listen = "127.0.0.1:0"
-       cfg.ManagementToken = arvadostest.ManagementToken
+       listen := "127.0.0.1:0"
+       cfg.cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: listen}] = arvados.ServiceInstance{}
+       cfg.cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: listen}] = arvados.ServiceInstance{}
+       cfg.cluster.ManagementToken = arvadostest.ManagementToken
+       cfg.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken
        s.testServer = &server{Config: cfg}
-       err := s.testServer.Start()
+       err = s.testServer.Start()
        c.Assert(err, check.Equals, nil)
 }
 
index 62db198dd9b9ef27618b9bfd04262b32ac2736f0..bade825b2c28591bf7ebef6eb2ec39998daaba6b 100644 (file)
@@ -15,7 +15,7 @@ import (
 )
 
 func (s *UnitSuite) TestStatus(c *check.C) {
-       h := handler{Config: DefaultConfig()}
+       h := handler{Config: DefaultConfig(s.Config)}
        u, _ := url.Parse("http://keep-web.example/status.json")
        req := &http.Request{
                Method:     "GET",
diff --git a/services/keep-web/usage.go b/services/keep-web/usage.go
deleted file mode 100644 (file)
index 705955b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package main
-
-import (
-       "encoding/json"
-       "flag"
-       "fmt"
-       "os"
-)
-
-func usage() {
-       c := DefaultConfig()
-       c.AnonymousTokens = []string{"xxxxxxxxxxxxxxxxxxxxxxx"}
-       c.Client.APIHost = "zzzzz.arvadosapi.com:443"
-       exampleConfigFile, err := json.MarshalIndent(c, "    ", "  ")
-       if err != nil {
-               panic(err)
-       }
-       fmt.Fprintf(os.Stderr, `
-
-Keep-web provides read-only HTTP access to files stored in Keep; see
-https://godoc.org/github.com/curoverse/arvados/services/keep-web and
-http://doc.arvados.org/install/install-keep-web.html
-
-Usage: keep-web -config path/to/keep-web.yml
-
-Options:
-`)
-       flag.PrintDefaults()
-       fmt.Fprintf(os.Stderr, `
-Example config file:
-    %s
-
-Client.APIHost:
-
-    Address (or address:port) of the Arvados API endpoint.
-
-Client.AuthToken:
-
-    Unused. Normally empty, or omitted entirely.
-
-Client.Insecure:
-
-    True if your Arvados API endpoint uses an unverifiable SSL/TLS
-    certificate.
-
-Listen:
-
-    Local port to listen on. Can be "address", "address:port", or
-    ":port", where "address" is a host IP address or name and "port"
-    is a port number or name.
-
-AnonymousTokens:
-
-    Array of tokens to try when a client does not provide a token.
-
-AttachmentOnlyHost:
-
-    Accept credentials, and add "Content-Disposition: attachment"
-    response headers, for requests at this hostname:port.
-
-    This prohibits inline display, which makes it possible to serve
-    untrusted and non-public content from a single origin, i.e.,
-    without wildcard DNS or SSL.
-
-TrustAllContent:
-
-    Serve non-public content from a single origin. Dangerous: read
-    docs before using!
-
-Cache.TTL:
-
-    Maximum time to cache manifests and permission checks.
-
-Cache.UUIDTTL:
-
-    Maximum time to cache collection state.
-
-Cache.MaxCollectionEntries:
-
-    Maximum number of collection cache entries.
-
-Cache.MaxCollectionBytes:
-
-    Approximate memory limit for collection cache.
-
-Cache.MaxPermissionEntries:
-
-    Maximum number of permission cache entries.
-
-Cache.MaxUUIDEntries:
-
-    Maximum number of UUID cache entries.
-
-`, exampleConfigFile)
-}