diff --git a/go.mod b/go.mod index 3da567126..6cb23d2f2 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/kubernetes-csi/external-snapshotter/v2 v2.1.0 github.com/kubesphere/sonargo v0.0.2 github.com/lib/pq v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.2.2 github.com/onsi/ginkgo v1.12.0 github.com/onsi/gomega v1.9.0 github.com/open-policy-agent/opa v0.18.0 diff --git a/pkg/apiserver/authentication/oauth/oauth_options.go b/pkg/apiserver/authentication/oauth/oauth_options.go index 878eb4a3f..5773cc1b2 100644 --- a/pkg/apiserver/authentication/oauth/oauth_options.go +++ b/pkg/apiserver/authentication/oauth/oauth_options.go @@ -17,9 +17,11 @@ limitations under the License. package oauth import ( + "encoding/json" "errors" "kubesphere.io/kubesphere/pkg/utils/sliceutil" "net/url" + "strings" "time" ) @@ -74,8 +76,57 @@ type Options struct { AccessTokenInactivityTimeout time.Duration `json:"accessTokenInactivityTimeout" yaml:"accessTokenInactivityTimeout"` } +// the type of key must be string type DynamicOptions map[string]interface{} +func (o DynamicOptions) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(desensitize(o)) + return data, err +} + +var ( + sensitiveKeys = [...]string{"password", "secret"} +) + +// isSensitiveData returns whether the input string contains sensitive information +func isSensitiveData(key string) bool { + for _, v := range sensitiveKeys { + if strings.Contains(strings.ToLower(key), v) { + return true + } + } + return false +} + +// desensitize returns the desensitized data +func desensitize(data map[string]interface{}) map[string]interface{} { + output := make(map[string]interface{}) + for k, v := range data { + if isSensitiveData(k) { + continue + } + switch v.(type) { + case map[interface{}]interface{}: + output[k] = desensitize(convert(v.(map[interface{}]interface{}))) + default: + output[k] = v + } + } + return output +} + +// convert returns formatted data +func convert(m map[interface{}]interface{}) map[string]interface{} { + output := make(map[string]interface{}) + for k, v := range m { + switch k.(type) { + case string: + output[k.(string)] = v + } + } + return output +} + type IdentityProviderOptions struct { // The provider name. Name string `json:"name" yaml:"name"` diff --git a/pkg/apiserver/authentication/oauth/oauth_options_test.go b/pkg/apiserver/authentication/oauth/oauth_options_test.go index df23586c6..b8b11b592 100644 --- a/pkg/apiserver/authentication/oauth/oauth_options_test.go +++ b/pkg/apiserver/authentication/oauth/oauth_options_test.go @@ -17,7 +17,9 @@ limitations under the License. package oauth import ( + "encoding/json" "github.com/google/go-cmp/cmp" + "gopkg.in/yaml.v3" "testing" "time" ) @@ -101,3 +103,42 @@ func TestClientResolveRedirectURL(t *testing.T) { } } } + +func TestDynamicOptions_MarshalJSON(t *testing.T) { + config := ` +accessTokenMaxAge: 1h +accessTokenInactivityTimeout: 30m +identityProviders: + - name: ldap + type: LDAPIdentityProvider + mappingMethod: auto + provider: + host: xxxx.sn.mynetname.net:389 + managerDN: uid=root,cn=users,dc=xxxx,dc=sn,dc=mynetname,dc=net + managerPassword: xxxx + userSearchBase: dc=xxxx,dc=sn,dc=mynetname,dc=net + loginAttribute: uid + mailAttribute: mail + - name: github + type: GitHubIdentityProvider + mappingMethod: mixed + provider: + clientID: 'xxxxxx' + clientSecret: 'xxxxxx' + endpoint: + authURL: 'https://github.com/login/oauth/authorize' + tokenURL: 'https://github.com/login/oauth/access_token' + redirectURL: 'https://ks-console/oauth/redirect' + scopes: + - user +` + var options Options + if err := yaml.Unmarshal([]byte(config), &options); err != nil { + t.Error(err) + } + expected := `{"identityProviders":[{"name":"ldap","mappingMethod":"auto","type":"LDAPIdentityProvider","provider":{"host":"xxxx.sn.mynetname.net:389","loginAttribute":"uid","mailAttribute":"mail","managerDN":"uid=root,cn=users,dc=xxxx,dc=sn,dc=mynetname,dc=net","userSearchBase":"dc=xxxx,dc=sn,dc=mynetname,dc=net"}},{"name":"github","mappingMethod":"mixed","type":"GitHubIdentityProvider","provider":{"clientID":"xxxxxx","endpoint":{"authURL":"https://github.com/login/oauth/authorize","tokenURL":"https://github.com/login/oauth/access_token"},"redirectURL":"https://ks-console/oauth/redirect","scopes":["user"]}}],"accessTokenMaxAge":3600000000000,"accessTokenInactivityTimeout":1800000000000}` + output, _ := json.Marshal(options) + if expected != string(output) { + t.Errorf("expected: %s, but got: %s", expected, output) + } +} diff --git a/pkg/kapis/config/v1alpha2/register.go b/pkg/kapis/config/v1alpha2/register.go index f73d5efe2..b0785d9df 100644 --- a/pkg/kapis/config/v1alpha2/register.go +++ b/pkg/kapis/config/v1alpha2/register.go @@ -18,11 +18,7 @@ package v1alpha2 import ( "github.com/emicklei/go-restful" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api" - "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" kubesphereconfig "kubesphere.io/kubesphere/pkg/apiserver/config" "kubesphere.io/kubesphere/pkg/apiserver/runtime" ) @@ -39,20 +35,7 @@ func AddToContainer(c *restful.Container, config *kubesphereconfig.Config) error webservice.Route(webservice.GET("/configs/oauth"). Doc("Information about the authorization server are published."). To(func(request *restful.Request, response *restful.Response) { - // workaround for this issue https://github.com/go-yaml/yaml/issues/139 - // fixed in gopkg.in/yaml.v3 - yamlData, err := yaml.Marshal(config.AuthenticationOptions.OAuthOptions) - if err != nil { - klog.Error(err) - api.HandleInternalError(response, request, err) - } - var options oauth.Options - err = yaml.Unmarshal(yamlData, &options) - if err != nil { - klog.Error(err) - api.HandleInternalError(response, request, err) - } - response.WriteEntity(options) + response.WriteEntity(config.AuthenticationOptions.OAuthOptions) })) webservice.Route(webservice.GET("/configs/configz").