diff --git a/go.mod b/go.mod index 79e6a1c19..b9e42bdc9 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( go.uber.org/automaxprocs v1.6.0 golang.org/x/crypto v0.46.0 golang.org/x/sys v0.39.0 - google.golang.org/api v0.257.0 + google.golang.org/api v0.258.0 google.golang.org/grpc v1.77.0 google.golang.org/protobuf v1.36.11 k8s.io/api v0.34.3 @@ -50,7 +50,7 @@ require ( sigs.k8s.io/mcs-api v0.3.0 ) -require golang.org/x/net v0.47.0 +require golang.org/x/net v0.48.0 require ( cloud.google.com/go/auth v0.17.0 // indirect @@ -182,7 +182,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/mod v0.30.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/term v0.38.0 // indirect golang.org/x/text v0.32.0 // indirect @@ -190,7 +190,7 @@ require ( golang.org/x/tools v0.39.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index b44700708..06b6a45aa 100644 --- a/go.sum +++ b/go.sum @@ -481,10 +481,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -553,16 +553,16 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= -google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= +google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= +google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/plugin/clouddns/README.md b/plugin/clouddns/README.md index 1e122813f..6a1e92e2e 100644 --- a/plugin/clouddns/README.md +++ b/plugin/clouddns/README.md @@ -35,8 +35,10 @@ clouddns [ZONE:PROJECT_ID:HOSTED_ZONE_NAME...] { * `credentials` is used for reading the credential file from **FILENAME** (normally a .json file). This field is optional. If this field is not provided then authentication will be done automatically, - e.g., through environmental variable `GOOGLE_APPLICATION_CREDENTIALS`. Please see - Google Cloud's [authentication method](https://cloud.google.com/docs/authentication) for more details. + e.g., through environmental variable `GOOGLE_APPLICATION_CREDENTIALS`. Note that CoreDNS validates that the given + file has a valid credentials type, but does not validate the credentials file for malicious input. Please see + Google Cloud's [authentication method](https://cloud.google.com/docs/authentication) and + [validating credential configurations from external sources](https://docs.cloud.google.com/docs/authentication/client-libraries#external-credentials) for more details. * `fallthrough` If zone matches and no record can be generated, pass request to the next plugin. If **[ZONES...]** is omitted, then fallthrough happens for all zones for which the plugin is diff --git a/plugin/clouddns/setup.go b/plugin/clouddns/setup.go index 343f6c705..64a76ccbc 100644 --- a/plugin/clouddns/setup.go +++ b/plugin/clouddns/setup.go @@ -2,6 +2,9 @@ package clouddns import ( "context" + "encoding/json" + "fmt" + "os" "strings" "github.com/coredns/caddy" @@ -67,7 +70,11 @@ func setup(c *caddy.Controller) error { c.RemainingArgs() case "credentials": if c.NextArg() { - opt = option.WithCredentialsFile(c.Val()) + credType, err := getCredType(c.Val()) + if err != nil { + return plugin.Error("clouddns", c.Errf("invalid credentials file %q: %v", c.Val(), err)) + } + opt = option.WithAuthCredentialsFile(credType, c.Val()) } else { return plugin.Error("clouddns", c.ArgErr()) } @@ -106,3 +113,30 @@ func setup(c *caddy.Controller) error { return nil } + +func getCredType(filename string) (option.CredentialsType, error) { + data, err := os.ReadFile(filename) + if err != nil { + return "", err + } + var f struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &f); err != nil { + return "", err + } + if f.Type == "" { + return "", fmt.Errorf("missing `type` field in credential") + } + + // Check against allowed types + ct := option.CredentialsType(f.Type) + switch ct { + case option.ServiceAccount, + option.AuthorizedUser, + option.ImpersonatedServiceAccount, + option.ExternalAccount: + return ct, nil + } + return "", fmt.Errorf("unknown credential type: %s", f.Type) +} diff --git a/plugin/clouddns/setup_test.go b/plugin/clouddns/setup_test.go index ae2262e7a..131026b29 100644 --- a/plugin/clouddns/setup_test.go +++ b/plugin/clouddns/setup_test.go @@ -2,6 +2,9 @@ package clouddns import ( "context" + "fmt" + "os" + "path/filepath" "testing" "github.com/coredns/caddy" @@ -14,6 +17,26 @@ func TestSetupCloudDNS(t *testing.T) { return fakeGCPClient{}, nil } + validCreds := filepath.Join(t.TempDir(), "valid_creds.json") + if err := os.WriteFile(validCreds, []byte(`{"type": "service_account"}`), 0644); err != nil { + t.Fatalf("Failed to create valid creds: %v", err) + } + + invalidTypeCreds := filepath.Join(t.TempDir(), "invalid_type_creds.json") + if err := os.WriteFile(invalidTypeCreds, []byte(`{"type": "bad_type"}`), 0644); err != nil { + t.Fatalf("Failed to create invalid creds: %v", err) + } + + emptyCreds := filepath.Join(t.TempDir(), "empty_creds.json") + if err := os.WriteFile(emptyCreds, []byte(`{}`), 0644); err != nil { + t.Fatalf("Failed to create empty creds: %v", err) + } + + invalidJSONCreds := filepath.Join(t.TempDir(), "invalid_json_creds.json") + if err := os.WriteFile(invalidJSONCreds, []byte(`{`), 0644); err != nil { + t.Fatalf("Failed to create invalid JSON creds: %v", err) + } + tests := []struct { body string expectedError bool @@ -38,6 +61,18 @@ func TestSetupCloudDNS(t *testing.T) { {`clouddns example.org { }`, true}, + {fmt.Sprintf(`clouddns example.org.:example-project:zone-name { + credentials %s +}`, validCreds), false}, + {fmt.Sprintf(`clouddns example.org.:example-project:zone-name { + credentials %s +}`, invalidTypeCreds), true}, + {fmt.Sprintf(`clouddns example.org.:example-project:zone-name { + credentials %s +}`, emptyCreds), true}, + {fmt.Sprintf(`clouddns example.org.:example-project:zone-name { + credentials %s +}`, invalidJSONCreds), true}, } for _, test := range tests {