mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
Merge pull request #4745 from github-vincent-miszczak/aws-sd-tags
feat(aws-sd): tag services
This commit is contained in:
commit
b834fef2b7
@ -12,7 +12,7 @@ Learn more about the API in the [AWS Cloud Map API Reference](https://docs.aws.a
|
||||
|
||||
## IAM Permissions
|
||||
|
||||
To use the AWS Cloud Map API, a user must have permissions to create the DNS namespace. Additionally you need to make sure that your nodes (on which External DNS runs) have an IAM instance profile with the `AWSCloudMapFullAccess` managed policy attached, that provides following permissions:
|
||||
To use the AWS Cloud Map API, a user must have permissions to create the DNS namespace. You need to make sure that your nodes (on which External DNS runs) have an IAM instance profile with the `AWSCloudMapFullAccess` managed policy attached, that provides following permissions:
|
||||
|
||||
```
|
||||
{
|
||||
@ -42,6 +42,82 @@ To use the AWS Cloud Map API, a user must have permissions to create the DNS nam
|
||||
}
|
||||
```
|
||||
|
||||
### IAM Permissions with ABAC
|
||||
You can use Attribute-based access control(ABAC) for advanced deployments.
|
||||
|
||||
You can define AWS tags that are applied to services created by the controller. By doing so, you can have precise control over your IAM policy to limit the scope of the permissions to services managed by the controller, rather than having to grant full permissions on your entire AWS account.
|
||||
To pass tags to service creation, use either CLI flags or environment variables:
|
||||
|
||||
*cli:* `--aws-sd-create-tag=key1=value1 --aws-sd-create-tag=key2=value2`
|
||||
|
||||
*environment:* `EXTERNAL_DNS_AWS_SD_CREATE_TAG=key1=value1\nkey2=value2`
|
||||
|
||||
Using tags, your `servicediscovery` policy can become:
|
||||
|
||||
```
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:ListNamespaces",
|
||||
"servicediscovery:ListServices"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:CreateService",
|
||||
"servicediscovery:TagResource"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:RequestTag/YOUR_TAG_KEY": "YOUR_TAG_VALUE"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:DiscoverInstances"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"servicediscovery:NamespaceName": "YOUR_NAMESPACE_NAME"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:RegisterInstance",
|
||||
"servicediscovery:DeregisterInstance",
|
||||
"servicediscovery:DeleteService",
|
||||
"servicediscovery:UpdateService"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/YOUR_TAG_KEY": "YOUR_TAG_VALUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Set up a namespace
|
||||
|
||||
Create a DNS namespace using the AWS Cloud Map API:
|
||||
|
2
main.go
2
main.go
@ -234,7 +234,7 @@ func main() {
|
||||
log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
|
||||
cfg.Registry = "aws-sd"
|
||||
}
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, cfg.AWSSDCreateTag, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
|
||||
case "azure-dns", "azure":
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.DryRun)
|
||||
case "azure-private-dns":
|
||||
|
@ -96,6 +96,7 @@ type Config struct {
|
||||
AWSPreferCNAME bool
|
||||
AWSZoneCacheDuration time.Duration
|
||||
AWSSDServiceCleanup bool
|
||||
AWSSDCreateTag map[string]string
|
||||
AWSZoneMatchParent bool
|
||||
AWSDynamoDBRegion string
|
||||
AWSDynamoDBTable string
|
||||
@ -257,6 +258,7 @@ var defaultConfig = &Config{
|
||||
AWSPreferCNAME: false,
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
AWSDynamoDBRegion: "",
|
||||
AWSDynamoDBTable: "external-dns",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
@ -359,7 +361,9 @@ var defaultConfig = &Config{
|
||||
|
||||
// NewConfig returns new Config object
|
||||
func NewConfig() *Config {
|
||||
return &Config{}
|
||||
return &Config{
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) String() string {
|
||||
@ -477,6 +481,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("aws-zones-cache-duration", "When using the AWS provider, set the zones list cache TTL (0s to disable).").Default(defaultConfig.AWSZoneCacheDuration.String()).DurationVar(&cfg.AWSZoneCacheDuration)
|
||||
app.Flag("aws-zone-match-parent", "Expand limit possible target by sub-domains (default: disabled)").BoolVar(&cfg.AWSZoneMatchParent)
|
||||
app.Flag("aws-sd-service-cleanup", "When using the AWS CloudMap provider, delete empty Services without endpoints (default: disabled)").BoolVar(&cfg.AWSSDServiceCleanup)
|
||||
app.Flag("aws-sd-create-tag", "When using the AWS CloudMap provider, add tag to created services. The flag can be used multiple times").StringMapVar(&cfg.AWSSDCreateTag)
|
||||
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure)").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
|
||||
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
|
||||
app.Flag("azure-subscription-id", "When using the Azure provider, override the Azure subscription to use (optional)").Default(defaultConfig.AzureSubscriptionID).StringVar(&cfg.AzureSubscriptionID)
|
||||
|
@ -68,6 +68,7 @@ var (
|
||||
AWSProfiles: []string{""},
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
AWSDynamoDBTable: "external-dns",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
@ -167,6 +168,7 @@ var (
|
||||
AWSProfiles: []string{"profile1", "profile2"},
|
||||
AWSZoneCacheDuration: 10 * time.Second,
|
||||
AWSSDServiceCleanup: true,
|
||||
AWSSDCreateTag: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
AWSDynamoDBTable: "custom-table",
|
||||
AzureConfigFile: "azure.json",
|
||||
AzureResourceGroup: "arg",
|
||||
@ -325,6 +327,8 @@ func TestParseFlags(t *testing.T) {
|
||||
"--aws-profile=profile2",
|
||||
"--aws-zones-cache-duration=10s",
|
||||
"--aws-sd-service-cleanup",
|
||||
"--aws-sd-create-tag=key1=value1",
|
||||
"--aws-sd-create-tag=key2=value2",
|
||||
"--no-aws-evaluate-target-health",
|
||||
"--policy=upsert-only",
|
||||
"--registry=noop",
|
||||
@ -436,6 +440,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_AWS_PROFILE": "profile1\nprofile2",
|
||||
"EXTERNAL_DNS_AWS_ZONES_CACHE_DURATION": "10s",
|
||||
"EXTERNAL_DNS_AWS_SD_SERVICE_CLEANUP": "true",
|
||||
"EXTERNAL_DNS_AWS_SD_CREATE_TAG": "key1=value1\nkey2=value2",
|
||||
"EXTERNAL_DNS_DYNAMODB_TABLE": "custom-table",
|
||||
"EXTERNAL_DNS_POLICY": "upsert-only",
|
||||
"EXTERNAL_DNS_REGISTRY": "noop",
|
||||
@ -504,8 +509,8 @@ func restoreEnv(t *testing.T, originalEnv map[string]string) {
|
||||
|
||||
func TestPasswordsNotLogged(t *testing.T) {
|
||||
cfg := Config{
|
||||
PDNSAPIKey: "pdns-api-key",
|
||||
RFC2136TSIGSecret: "tsig-secret",
|
||||
PDNSAPIKey: "pdns-api-key",
|
||||
RFC2136TSIGSecret: "tsig-secret",
|
||||
}
|
||||
|
||||
s := cfg.String()
|
||||
|
@ -79,10 +79,12 @@ type AWSSDProvider struct {
|
||||
cleanEmptyService bool
|
||||
// filter services for removal
|
||||
ownerID string
|
||||
// tags to be added to the service
|
||||
tags []sdtypes.Tag
|
||||
}
|
||||
|
||||
// NewAWSSDProvider initializes a new AWS Cloud Map based Provider.
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, dryRun, cleanEmptyService bool, ownerID string, client AWSSDClient) (*AWSSDProvider, error) {
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, dryRun, cleanEmptyService bool, ownerID string, tags map[string]string, client AWSSDClient) (*AWSSDProvider, error) {
|
||||
p := &AWSSDProvider{
|
||||
client: client,
|
||||
dryRun: dryRun,
|
||||
@ -90,6 +92,7 @@ func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string,
|
||||
namespaceTypeFilter: newSdNamespaceFilter(namespaceType),
|
||||
cleanEmptyService: cleanEmptyService,
|
||||
ownerID: ownerID,
|
||||
tags: awsTags(tags),
|
||||
}
|
||||
|
||||
return p, nil
|
||||
@ -113,6 +116,15 @@ func newSdNamespaceFilter(namespaceTypeConfig string) sdtypes.NamespaceFilter {
|
||||
}
|
||||
}
|
||||
|
||||
// awsTags converts user supplied tags to AWS format
|
||||
func awsTags(tags map[string]string) []sdtypes.Tag {
|
||||
awsTags := make([]sdtypes.Tag, 0, len(tags))
|
||||
for k, v := range tags {
|
||||
awsTags = append(awsTags, sdtypes.Tag{Key: aws.String(k), Value: aws.String(v)})
|
||||
}
|
||||
return awsTags
|
||||
}
|
||||
|
||||
// Records returns list of all endpoints.
|
||||
func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) {
|
||||
namespaces, err := p.ListNamespaces(ctx)
|
||||
@ -400,6 +412,7 @@ func (p *AWSSDProvider) CreateService(ctx context.Context, namespaceID *string,
|
||||
}},
|
||||
},
|
||||
NamespaceId: namespaceID,
|
||||
Tags: p.tags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -900,3 +900,39 @@ func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
|
||||
|
||||
assert.Len(t, instances["srv1"], 0)
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_awsTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
Expectation []sdtypes.Tag
|
||||
Input map[string]string
|
||||
}{
|
||||
{
|
||||
Expectation: []sdtypes.Tag{
|
||||
{
|
||||
Key: aws.String("key1"),
|
||||
Value: aws.String("value1"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("key2"),
|
||||
Value: aws.String("value2"),
|
||||
},
|
||||
},
|
||||
Input: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Expectation: []sdtypes.Tag{},
|
||||
Input: map[string]string{},
|
||||
},
|
||||
{
|
||||
Expectation: []sdtypes.Tag{},
|
||||
Input: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
assert.EqualValues(t, test.Expectation, awsTags(test.Input))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user