14287: Handle CORS OPTIONS requests.
[arvados.git] / lib / config / export.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package config
6
7 import (
8         "encoding/json"
9         "errors"
10         "fmt"
11         "io"
12         "strings"
13
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15 )
16
17 // ExportJSON writes a JSON object with the safe (non-secret) portions
18 // of the cluster config to w.
19 func ExportJSON(w io.Writer, cluster *arvados.Cluster) error {
20         buf, err := json.Marshal(cluster)
21         if err != nil {
22                 return err
23         }
24         var m map[string]interface{}
25         err = json.Unmarshal(buf, &m)
26         if err != nil {
27                 return err
28         }
29         err = redactUnsafe(m, "", "")
30         if err != nil {
31                 return err
32         }
33         return json.NewEncoder(w).Encode(m)
34 }
35
36 // whitelist classifies configs as safe/unsafe to reveal to
37 // unauthenticated clients.
38 //
39 // Every config entry must either be listed explicitly here along with
40 // all of its parent keys (e.g., "API" + "API.RequestTimeout"), or
41 // have an ancestor listed as false (e.g.,
42 // "PostgreSQL.Connection.password" has an ancestor
43 // "PostgreSQL.Connection" with a false value). Otherwise, it is a bug
44 // which should be caught by tests.
45 //
46 // Example: API.RequestTimeout is safe because whitelist["API"] == and
47 // whitelist["API.RequestTimeout"] == true.
48 //
49 // Example: PostgreSQL.Connection.password is not safe because
50 // whitelist["PostgreSQL.Connection"] == false.
51 //
52 // Example: PostgreSQL.BadKey would cause an error because
53 // whitelist["PostgreSQL"] isn't false, and neither
54 // whitelist["PostgreSQL.BadKey"] nor whitelist["PostgreSQL.*"]
55 // exists.
56 var whitelist = map[string]bool{
57         // | sort -t'"' -k2,2
58         "API":                                        true,
59         "API.AsyncPermissionsUpdateInterval":         false,
60         "API.DisabledAPIs":                           false,
61         "API.MaxIndexDatabaseRead":                   false,
62         "API.MaxItemsPerResponse":                    true,
63         "API.MaxRequestAmplification":                false,
64         "API.MaxRequestSize":                         true,
65         "API.RailsSessionSecretToken":                false,
66         "API.RequestTimeout":                         true,
67         "AuditLogs":                                  false,
68         "AuditLogs.MaxAge":                           false,
69         "AuditLogs.MaxDeleteBatch":                   false,
70         "AuditLogs.UnloggedAttributes":               false,
71         "Collections":                                true,
72         "Collections.BlobSigning":                    true,
73         "Collections.BlobSigningKey":                 false,
74         "Collections.BlobSigningTTL":                 true,
75         "Collections.CollectionVersioning":           false,
76         "Collections.DefaultReplication":             true,
77         "Collections.DefaultTrashLifetime":           true,
78         "Collections.PreserveVersionIfIdle":          true,
79         "Collections.TrashSweepInterval":             false,
80         "Containers":                                 true,
81         "Containers.CloudVMs":                        false,
82         "Containers.DefaultKeepCacheRAM":             true,
83         "Containers.DispatchPrivateKey":              false,
84         "Containers.JobsAPI":                         true,
85         "Containers.JobsAPI.CrunchJobUser":           false,
86         "Containers.JobsAPI.CrunchJobWrapper":        false,
87         "Containers.JobsAPI.CrunchRefreshTrigger":    false,
88         "Containers.JobsAPI.DefaultDockerImage":      false,
89         "Containers.JobsAPI.Enable":                  true,
90         "Containers.JobsAPI.GitInternalDir":          false,
91         "Containers.JobsAPI.ReuseJobIfOutputsDiffer": false,
92         "Containers.Logging":                         false,
93         "Containers.LogReuseDecisions":               false,
94         "Containers.MaxComputeVMs":                   false,
95         "Containers.MaxDispatchAttempts":             false,
96         "Containers.MaxRetryAttempts":                true,
97         "Containers.SLURM":                           false,
98         "Containers.StaleLockTimeout":                false,
99         "Containers.SupportedDockerImageFormats":     true,
100         "Containers.UsePreemptibleInstances":         true,
101         "Git":                                        false,
102         "InstanceTypes":                              true,
103         "InstanceTypes.*":                            true,
104         "InstanceTypes.*.*":                          true,
105         "Login":                                      false,
106         "Mail":                                       false,
107         "ManagementToken":                            false,
108         "PostgreSQL":                                 false,
109         "RemoteClusters":                             true,
110         "RemoteClusters.*":                           true,
111         "RemoteClusters.*.ActivateUsers":             true,
112         "RemoteClusters.*.Host":                      true,
113         "RemoteClusters.*.Insecure":                  true,
114         "RemoteClusters.*.Proxy":                     true,
115         "RemoteClusters.*.Scheme":                    true,
116         "Services":                                   true,
117         "Services.*":                                 true,
118         "Services.*.ExternalURL":                     true,
119         "Services.*.InternalURLs":                    false,
120         "SystemLogs":                                 false,
121         "SystemRootToken":                            false,
122         "TLS":                                        false,
123         "Users":                                      false,
124         "Workbench":                                  false,
125 }
126
127 func redactUnsafe(m map[string]interface{}, mPrefix, lookupPrefix string) error {
128         var errs []string
129         for k, v := range m {
130                 lookupKey := k
131                 safe, ok := whitelist[lookupPrefix+k]
132                 if !ok {
133                         lookupKey = "*"
134                         safe, ok = whitelist[lookupPrefix+"*"]
135                 }
136                 if !ok {
137                         errs = append(errs, fmt.Sprintf("config bug: key %q not in whitelist map", lookupPrefix+k))
138                         continue
139                 }
140                 if !safe {
141                         delete(m, k)
142                         continue
143                 }
144                 if v, ok := v.(map[string]interface{}); ok {
145                         err := redactUnsafe(v, mPrefix+k+".", lookupPrefix+lookupKey+".")
146                         if err != nil {
147                                 errs = append(errs, err.Error())
148                         }
149                 }
150         }
151         if len(errs) > 0 {
152                 return errors.New(strings.Join(errs, "\n"))
153         }
154         return nil
155 }