diff --git a/core/provider/errors.go b/core/provider/errors.go index 3ed442206..9371dee00 100644 --- a/core/provider/errors.go +++ b/core/provider/errors.go @@ -27,4 +27,6 @@ var ( ErrAppealValidationInvalidDurationValue = errors.New("invalid duration value") ErrAppealValidationMissingRequiredParameter = errors.New("missing required parameter") ErrAppealValidationMissingRequiredQuestion = errors.New("missing required question") + + ErrGrantAlreadyExists = errors.New("grant already exists") ) diff --git a/domain/provider.go b/domain/provider.go index cdde9cd67..8d6f859c8 100644 --- a/domain/provider.go +++ b/domain/provider.go @@ -19,6 +19,7 @@ const ( ProviderTypeGitlab = "gitlab" ProviderTypeGate = "gate" ProviderTypeMaxCompute = "maxcompute" + ProviderTypeOss = "oss" ) // Role is the configuration to define a role and mapping the permissions in the provider diff --git a/go.mod b/go.mod index e3adb4e41..661add30f 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,12 @@ require ( cloud.google.com/go/storage v1.30.1 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/MakeNowJust/heredoc v1.0.0 - github.com/alibabacloud-go/darabonba-openapi v0.1.16 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8 + github.com/alibabacloud-go/darabonba-openapi v0.2.1 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 github.com/alibabacloud-go/maxcompute-20220104 v1.4.1 github.com/alibabacloud-go/sts-20150401 v1.1.2 github.com/aliyun/aliyun-odps-go-sdk v0.3.15 + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/antonmedv/expr v1.15.3 github.com/envoyproxy/protoc-gen-validate v1.0.2 github.com/go-playground/validator/v10 v10.4.1 @@ -50,7 +51,7 @@ require ( go.opentelemetry.io/otel/metric v1.29.0 go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/sdk/metric v1.27.0 - golang.org/x/net v0.22.0 + golang.org/x/net v0.23.0 golang.org/x/oauth2 v0.22.0 golang.org/x/sync v0.4.0 google.golang.org/api v0.128.0 @@ -72,13 +73,13 @@ require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/alecthomas/chroma v0.8.2 // indirect - github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.0 // indirect github.com/alibabacloud-go/tea v1.2.2 // indirect github.com/alibabacloud-go/tea-utils v1.4.3 // indirect - github.com/alibabacloud-go/tea-utils/v2 v2.0.5 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.3.10 // indirect github.com/andybalholm/brotli v1.0.4 // indirect @@ -178,7 +179,7 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/tjfoc/gmsm v1.3.2 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 // indirect diff --git a/go.sum b/go.sum index 3e15d5b07..72d34b67a 100644 --- a/go.sum +++ b/go.sum @@ -165,13 +165,28 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= -github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= -github.com/alibabacloud-go/darabonba-openapi v0.1.16 h1:f6ZspWKTBurQzyLpZKMVxO51HAePY8aedicwuX3+E20= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= github.com/alibabacloud-go/darabonba-openapi v0.1.16/go.mod h1:ZjyqRbbZOaUBSh7keeH8VQN/BzCPvxCQwMuJGDdbmXQ= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8 h1:benoD0QHDrylMzEQVpX/6uKtrN8LohT66ZlKXVJh7pM= +github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= +github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= @@ -191,20 +206,27 @@ github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeG github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.4.3 h1:8SzwmmRrOnQ09Hf5a9GyfJc0d7Sjv6fmsZoF4UDbFjo= github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= -github.com/alibabacloud-go/tea-utils/v2 v2.0.5 h1:EUakYEUAwr6L3wLT0vejIw2rc0IA1RSXDwLnIb3f2vU= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/aliyun-odps-go-sdk v0.3.15 h1:HkWki3g7G0xEAyxSAChqSDxLw8NCl7PFc8KxcECXReQ= github.com/aliyun/aliyun-odps-go-sdk v0.3.15/go.mod h1:t/tgF/iN5aAs/gLL7sEI8/qdax4NuFCKEjO3OJbHZqI= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA= github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -1408,8 +1430,9 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= @@ -1600,6 +1623,7 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1614,6 +1638,8 @@ golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80 golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1721,6 +1747,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1763,8 +1790,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1958,6 +1986,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1974,6 +2004,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/server/services.go b/internal/server/services.go index dcb6a2c03..7b67a86bd 100644 --- a/internal/server/services.go +++ b/internal/server/services.go @@ -32,6 +32,7 @@ import ( "github.com/goto/guardian/plugins/providers/maxcompute" "github.com/goto/guardian/plugins/providers/metabase" "github.com/goto/guardian/plugins/providers/noop" + "github.com/goto/guardian/plugins/providers/oss" "github.com/goto/guardian/plugins/providers/shield" "github.com/goto/guardian/plugins/providers/tableau" "github.com/goto/salt/audit" @@ -125,6 +126,7 @@ func InitServices(deps ServiceDeps) (*Services, error) { gitlab.NewProvider(domain.ProviderTypeGitlab, deps.Crypto, deps.Logger), gate.NewProvider(domain.ProviderTypeGate, deps.Crypto), maxcompute.New(domain.ProviderTypeMaxCompute, deps.Crypto, deps.Logger), + oss.NewProvider(domain.ProviderTypeOss, deps.Crypto), } iamManager := identities.NewManager(deps.Crypto, deps.Validator) diff --git a/plugins/providers/oss/config.go b/plugins/providers/oss/config.go new file mode 100644 index 000000000..473965181 --- /dev/null +++ b/plugins/providers/oss/config.go @@ -0,0 +1,94 @@ +package oss + +import ( + "context" + "fmt" + + "github.com/goto/guardian/domain" + "github.com/goto/guardian/utils" + "github.com/mitchellh/mapstructure" +) + +const ( + AccountTypeRAMUser = "ram_user" + AccountTypeRAMRole = "ram_role" + resourceTypeBucket = "bucket" +) + +var validResourceTypes = []string{resourceTypeBucket} + +type config struct { + *domain.ProviderConfig + crypto domain.Crypto +} + +func NewConfig(pc *domain.ProviderConfig, crypto domain.Crypto) *config { + return &config{ + ProviderConfig: pc, + crypto: crypto, + } +} + +func (c *config) ParseAndValidate() error { + if c.Credentials == nil { + return fmt.Errorf("credentials is required") + } + + creds, err := c.getCredentials() + if err != nil { + return err + } + + if err := creds.validate(); err != nil { + return fmt.Errorf("invalid credentials: %w", err) + } + + // validate resource config + for _, rc := range c.Resources { + if !utils.ContainsString(validResourceTypes, rc.Type) { + return fmt.Errorf("invalid resource type: %q", rc.Type) + } + + for _, role := range rc.Roles { + if len(role.Permissions) == 0 { + return fmt.Errorf("permissions are missing for role: %q", role.Name) + } + for _, permission := range role.Permissions { + // TODO: validate permissions + _, ok := permission.(string) + if !ok { + return fmt.Errorf("unexpected permission type: %T, expected: string", permission) + } + } + } + } + return nil +} + +func (c *config) EncryptCredentials(ctx context.Context) error { + creds, err := c.getCredentials() + if err != nil { + return err + } + + if err := creds.encrypt(c.crypto); err != nil { + return fmt.Errorf("unable to encrypt credentials: %w", err) + } + + c.Credentials = creds + return nil +} + +func (c *config) getCredentials() (*Credentials, error) { + if creds, ok := c.Credentials.(Credentials); ok { // parsed + return &creds, nil + } else if mapCreds, ok := c.Credentials.(map[string]interface{}); ok { // not parsed + var creds Credentials + if err := mapstructure.Decode(mapCreds, &creds); err != nil { + return nil, fmt.Errorf("unable to decode credentials: %w", err) + } + return &creds, nil + } + + return nil, fmt.Errorf("invalid credentials type: %T", c.Credentials) +} diff --git a/plugins/providers/oss/credentials.go b/plugins/providers/oss/credentials.go new file mode 100644 index 000000000..da03b4859 --- /dev/null +++ b/plugins/providers/oss/credentials.go @@ -0,0 +1,47 @@ +package oss + +import ( + "errors" + + "github.com/goto/guardian/domain" +) + +type Credentials struct { + AccessKeyID string `mapstructure:"access_key_id" json:"access_key_id"` + AccessKeySecret string `mapstructure:"access_key_secret" json:"access_key_secret"` + RAMRole string `mapstructure:"ram_role" json:"ram_role"` + RegionID string `mapstructure:"region_id" json:"region_id"` +} + +func (c *Credentials) validate() error { + if c.AccessKeyID == "" { + return errors.New("access_key_id is required") + } + if c.AccessKeySecret == "" { + return errors.New("access_key_secret is required") + } + if c.RegionID == "" { + return errors.New("region_id is required") + } + return nil +} + +func (c *Credentials) encrypt(encryptor domain.Encryptor) error { + encryptedAccessKeySecret, err := encryptor.Encrypt(c.AccessKeySecret) + if err != nil { + return err + } + + c.AccessKeySecret = encryptedAccessKeySecret + return nil +} + +func (c *Credentials) decrypt(decryptor domain.Decryptor) error { + decryptedAccessKeySecret, err := decryptor.Decrypt(c.AccessKeySecret) + if err != nil { + return err + } + + c.AccessKeySecret = decryptedAccessKeySecret + return nil +} diff --git a/plugins/providers/oss/provider.go b/plugins/providers/oss/provider.go new file mode 100644 index 000000000..80f49c0e0 --- /dev/null +++ b/plugins/providers/oss/provider.go @@ -0,0 +1,497 @@ +package oss + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "slices" + "strings" + "sync" + + pv "github.com/goto/guardian/core/provider" + "github.com/goto/guardian/domain" + sts "github.com/goto/guardian/pkg/stsClient" + "github.com/goto/guardian/utils" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +//go:generate mockery --name=encryptor --exported --with-expecter +type encryptor interface { + domain.Crypto +} + +type PolicyStatement struct { + Action []string `json:"Action"` + Effect string `json:"Effect"` + Principal []string `json:"Principal"` + Resource []string `json:"Resource"` +} + +type Policy struct { + Version string `json:"Version"` + Statement []PolicyStatement `json:"Statement"` +} + +type OSSClient struct { + client *oss.Client + stsClientExist bool +} +type provider struct { + pv.UnimplementedClient + pv.PermissionManager + typeName string + encryptor encryptor + + ossClients map[string]OSSClient + sts *sts.Sts + + mu sync.Mutex +} + +func NewProvider(typeName string, encryptor encryptor) *provider { + return &provider{ + typeName: typeName, + encryptor: encryptor, + ossClients: make(map[string]OSSClient), + sts: sts.NewSTS(), + } +} + +func (p *provider) GetType() string { + return p.typeName +} + +func (p *provider) GetAccountTypes() []string { + return []string{AccountTypeRAMUser, AccountTypeRAMRole} +} + +func (p *provider) GetRoles(pc *domain.ProviderConfig, resourceType string) ([]*domain.Role, error) { + return pv.GetRoles(pc, resourceType) +} + +func (p *provider) CreateConfig(pc *domain.ProviderConfig) error { + c := NewConfig(pc, p.encryptor) + + ctx := context.TODO() + if err := c.ParseAndValidate(); err != nil { + return err + } + + return c.EncryptCredentials(ctx) +} + +func (p *provider) GetResources(ctx context.Context, pc *domain.ProviderConfig) ([]*domain.Resource, error) { + client, err := p.getOSSClient(pc, "") + if err != nil { + return nil, err + } + + resources := make([]*domain.Resource, 0) + availableResourceTypes := pc.GetResourceTypes() + + marker := "" + for { + listBucketsResp, err := client.ListBuckets(oss.Marker(marker)) + if err != nil { + return nil, fmt.Errorf("failed to list buckets: %w", err) + } + + accountID := listBucketsResp.Owner.ID + + // By default, a maximum of 100 buckets are listed at a time. + for _, bucket := range listBucketsResp.Buckets { + if slices.Contains(availableResourceTypes, resourceTypeBucket) { + resources = append(resources, &domain.Resource{ + ProviderType: pc.Type, + ProviderURN: pc.URN, + Type: resourceTypeBucket, + URN: bucket.Name, + Name: bucket.Name, + GlobalURN: utils.GetGlobalURN("oss", accountID, resourceTypeBucket, bucket.Name), + }) + } + } + + if !listBucketsResp.IsTruncated { + break + } + marker = listBucketsResp.NextMarker + } + + return resources, nil +} + +func (p *provider) GrantAccess(ctx context.Context, pc *domain.ProviderConfig, g domain.Grant) error { + if g.Resource.Type != resourceTypeBucket { + return fmt.Errorf("unsupported resource type: %s", g.Resource.Type) + } + + if len(g.Permissions) == 0 { + return fmt.Errorf("no permissions in grant") + } + + ramRole, err := getRAMRole(g) + if err != nil { + return err + } + + client, err := p.getOSSClient(pc, ramRole) + if err != nil { + return err + } + + existingPolicy, err := client.GetBucketPolicy(g.Resource.URN) + if err != nil { + var ossErr oss.ServiceError + if errors.As(err, &ossErr) && ossErr.StatusCode == http.StatusNotFound { + existingPolicy = `{"Version":"1","Statement":[]}` + } else { + return fmt.Errorf("failed to get bucket Policy: %w", err) + } + } + + bucketPolicy, err := updatePolicyToGrantPermissions(existingPolicy, g) + if err != nil { + if errors.Is(err, pv.ErrGrantAlreadyExists) { + return nil + } + return err + } + + err = client.SetBucketPolicy(g.Resource.URN, bucketPolicy) + if err != nil { + return fmt.Errorf("failed to set bucket policy: %w", err) + } + + return nil +} + +func (p *provider) RevokeAccess(ctx context.Context, pc *domain.ProviderConfig, g domain.Grant) error { + if g.Resource.Type != resourceTypeBucket { + return fmt.Errorf("unsupported resource type: %s", g.Resource.Type) + } + + if len(g.Permissions) == 0 { + return nil + } + + ramRole, err := getRAMRole(g) + if err != nil { + return err + } + + client, err := p.getOSSClient(pc, ramRole) + if err != nil { + return err + } + + existingPolicy, err := client.GetBucketPolicy(g.Resource.URN) + if err != nil { + var ossErr oss.ServiceError + if errors.As(err, &ossErr) && ossErr.StatusCode == http.StatusNotFound { + return fmt.Errorf("access not found for role: %s", g.Role) + } else { + return fmt.Errorf("failed to get bucket Policy: %w", err) + } + } + + bucketPolicy, err := revokePermissionsFromPolicy(existingPolicy, g) + if err != nil { + return err + } + + if bucketPolicy == "" { + err = client.DeleteBucketPolicy(g.Resource.URN) + if err != nil { + return fmt.Errorf("failed to delete bucket policy: %w", err) + } + return nil + } + + err = client.SetBucketPolicy(g.Resource.URN, bucketPolicy) + if err != nil { + return fmt.Errorf("failed to set bucket policy: %w", err) + } + + return nil +} + +func policyStatementExist(statement PolicyStatement, resourceAccountID string, g domain.Grant) bool { + resourceMatch := slices.Contains(statement.Resource, fmt.Sprintf("acs:oss:*:%s:%s", resourceAccountID, g.Resource.URN)) + if !resourceMatch { + return false + } + + if len(statement.Action) != len(g.Permissions) { + return false + } + + for _, action := range statement.Action { + if !slices.Contains(g.Permissions, action) { + return false + } + } + return true + +} + +func removePrincipalFromPolicy(statement PolicyStatement, principalAccountID string) PolicyStatement { + var updatedPrincipals []string + for _, principal := range statement.Principal { + if principal == principalAccountID { + continue + } + + updatedPrincipals = append(updatedPrincipals, principal) + } + + statement.Principal = updatedPrincipals + return statement +} + +func revokePermissionsFromPolicy(policyString string, g domain.Grant) (string, error) { + bucketPolicy, err := unmarshalPolicy(policyString) + if err != nil { + return "", err + } + + principalAccountID, err := getPrincipalFromAccountID(g.AccountID) + if err != nil { + return "", err + } + resourceAccountID, err := getAccountIDFromResource(g.Resource) + if err != nil { + return "", err + } + + statements, matchingStatements := findStatementsWithMatchingActions(bucketPolicy, resourceAccountID, g) + if len(matchingStatements) == 0 { + return policyString, nil + } + + statementFoundToRevokePermission := false + for _, statement := range matchingStatements { + if !slices.Contains(statement.Principal, principalAccountID) { + statements = append(statements, statement) + continue + } + + // revoke access of the principal + updatedStatement := removePrincipalFromPolicy(statement, principalAccountID) + if len(updatedStatement.Principal) > 0 { + statements = append(statements, updatedStatement) + } + statementFoundToRevokePermission = true + } + + if !statementFoundToRevokePermission { + return "", fmt.Errorf("access not found for role: %s", g.Role) + } + + bucketPolicy.Statement = statements + if len(bucketPolicy.Statement) == 0 { + return "", nil + } + + marshaledPolicy, err := marshalPolicy(bucketPolicy) + if err != nil { + return "", err + } + + return marshaledPolicy, nil +} + +func updatePolicyToGrantPermissions(policy string, g domain.Grant) (string, error) { + bucketPolicy, err := unmarshalPolicy(policy) + if err != nil { + return "", err + } + + principalAccountID, err := getPrincipalFromAccountID(g.AccountID) + if err != nil { + return "", err + } + + resourceAccountID, err := getAccountIDFromResource(g.Resource) + if err != nil { + return "", err + } + + statements, matchingStatements := findStatementsWithMatchingActions(bucketPolicy, resourceAccountID, g) + statementToUpdate := PolicyStatement{ + Action: g.Permissions, + Effect: "Allow", + Principal: []string{principalAccountID}, + Resource: []string{fmt.Sprintf("acs:oss:*:%s:%s", resourceAccountID, g.Resource.URN)}, + } + + foundStatementToUpdate := false + for _, statement := range matchingStatements { + if slices.Contains(statement.Principal, principalAccountID) { + return "", pv.ErrGrantAlreadyExists + } + + if !foundStatementToUpdate { + foundStatementToUpdate = true + statement.Principal = append(statement.Principal, principalAccountID) + } + + statements = append(statements, statement) + } + + // if no matching statement found, add the new statement + if !foundStatementToUpdate { + statements = append(statements, statementToUpdate) + } + + bucketPolicy.Statement = statements + marshaledPolicy, err := marshalPolicy(bucketPolicy) + if err != nil { + return "", err + } + + return marshaledPolicy, nil +} + +func findStatementsWithMatchingActions(bucketPolicy Policy, resourceAccountID string, g domain.Grant) ([]PolicyStatement, []PolicyStatement) { + var statements []PolicyStatement + var matchingStatements []PolicyStatement + for _, statement := range bucketPolicy.Statement { + if policyStatementExist(statement, resourceAccountID, g) { + matchingStatements = append(matchingStatements, statement) + } else { + statements = append(statements, statement) + } + } + return statements, matchingStatements +} + +func (p *provider) getCreds(pc *domain.ProviderConfig) (*Credentials, error) { + cfg := &config{pc, p.encryptor} + creds, err := cfg.getCredentials() + if err != nil { + return nil, err + } + if err := creds.decrypt(p.encryptor); err != nil { + return nil, fmt.Errorf("failed to decrypt credentials: %w", err) + } + return creds, nil +} + +func (p *provider) getOSSClient(pc *domain.ProviderConfig, ramRole string) (*oss.Client, error) { + creds, err := p.getCreds(pc) + if err != nil { + return nil, err + } + + if ramRole == "" { + ramRole = creds.RAMRole + } + + stsClientID := "oss-" + ramRole + if ossClient, ok := p.getCachedOSSClient(ramRole, stsClientID, pc.URN); ok { + return ossClient, nil + } + + endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", creds.RegionID) + var client *oss.Client + if ramRole != "" { + stsClient, err := p.sts.GetSTSClient(stsClientID, creds.AccessKeyID, creds.AccessKeySecret, creds.RegionID) + if err != nil { + return nil, err + } + + clientConfig, err := sts.AssumeRole(stsClient, creds.RAMRole, pc.URN, creds.RegionID) + if err != nil { + return nil, err + } + + clientOpts := oss.SecurityToken(*clientConfig.SecurityToken) + client, err = oss.New(endpoint, *clientConfig.AccessKeyId, *clientConfig.AccessKeySecret, clientOpts) + if err != nil { + return nil, fmt.Errorf("failed to initialize oss client: %w", err) + } + } else { + client, err = oss.New(endpoint, creds.AccessKeyID, creds.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("failed to initialize oss client: %w", err) + } + } + + p.mu.Lock() + if ramRole != "" { + p.ossClients[ramRole] = OSSClient{client: client, stsClientExist: true} + } else { + p.ossClients[pc.URN] = OSSClient{client: client} + } + p.mu.Unlock() + return client, nil +} + +func (p *provider) getCachedOSSClient(ramRole, stsClientID, urn string) (*oss.Client, bool) { + if c, ok := p.ossClients[ramRole]; ok { + if c.stsClientExist && p.sts.IsSTSTokenValid(stsClientID) { + return c.client, true + } + return c.client, true + } + + if c, ok := p.ossClients[urn]; ok { + return c.client, true + } + + return nil, false +} + +func getRAMRole(g domain.Grant) (string, error) { + resourceAccountID, err := getAccountIDFromResource(g.Resource) + if err != nil { + return "", err + } + return fmt.Sprintf("acs:ram::%s:role/guardian.bot", resourceAccountID), nil +} + +func getAccountIDFromResource(resource *domain.Resource) (string, error) { + urnParts := strings.Split(resource.GlobalURN, ":") + if len(urnParts) < 3 { + return "", fmt.Errorf("invalid GlobalURN format") + } + return urnParts[2], nil +} + +func getPrincipalFromAccountID(accountID string) (string, error) { + accountIDParts := strings.Split(accountID, "$") + if len(accountIDParts) < 2 { + return "", fmt.Errorf("invalid accountID format") + } + + subParts := strings.Split(accountIDParts[1], ":") + if len(subParts) < 2 { + return "", fmt.Errorf("invalid accountID format") + } + + return subParts[1], nil +} + +func unmarshalPolicy(policy string) (Policy, error) { + var bucketPolicy Policy + if err := json.Unmarshal([]byte(policy), &bucketPolicy); err != nil { + return Policy{}, fmt.Errorf("failed to unmarshal existing policy: %w", err) + } + return bucketPolicy, nil +} + +func marshalPolicy(policy Policy) (string, error) { + if len(policy.Statement) == 0 { + policy.Statement = make([]PolicyStatement, 0) + } + + bucketPolicyBytes, err := json.Marshal(policy) + if err != nil { + return "", fmt.Errorf("failed to marshal updated policy: %w", err) + } + return string(bucketPolicyBytes), nil +}