Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ This document lists every service and operation available in CloudEmu across all
| 22 | Machine Learning | `sagemaker` (+ `sagemaker-runtime`) | `azureai` (CognitiveServices + MachineLearningServices) | `vertexai` |
| 23 | AI Search | — | `azuresearch` (Microsoft.Search) | — |
| 24 | Container Orchestration | `ecs` | — | — |
| 25 | DNS Resolver | `route53resolver` | — | — |

---

Expand Down Expand Up @@ -2218,6 +2219,47 @@ real EC2 instance subject to managed-resource visibility.

---

## 25. DNS Resolver

**Driver interface:** `services/route53resolver/driver/driver.go`
**AWS:** Route 53 Resolver (`Route53Resolver.*`, AWS JSON 1.1) | **Azure:** — | **GCP:** —

AWS-only. Real `aws-sdk-go-v2/service/route53resolver` clients work against the
SDK-compat server (`awsserver.Drivers{Route53Resolver: cloud.Route53Resolver}`).
Full parity: **all 72 SDK operations**, no stubs. Each resource group is stored
in an in-memory `memstore.Store` guarded by a single mutex; reads are
copy-on-write clones. Every group is covered by a real-SDK round-trip test.

Per-VPC configs (Resolver autodefined-reverse, DNSSEC validation, firewall
fail-open) are **lazily materialized** on first Get with their AWS defaults
(reverse ENABLED, DNSSEC DISABLED, fail-open DISABLED) and only appear in the
corresponding List once touched. Firewall rules are identified within a group by
`(FirewallDomainListId, Qtype)`; deleting a rule group cascades to its rules.

| Family | Operations |
|--------|-----------|
| Resolver endpoints | Create/Get/Update/Delete/ListResolverEndpoint(s), Associate/DisassociateResolverEndpointIpAddress, ListResolverEndpointIpAddresses |
| Resolver rules | Create/Get/Update/Delete/ListResolverRule(s), Associate/DisassociateResolverRule, Get/ListResolverRuleAssociation(s), Put/GetResolverRulePolicy |
| Query-log configs | Create/Get/Delete/ListResolverQueryLogConfig(s), Associate/DisassociateResolverQueryLogConfig, Get/ListResolverQueryLogConfigAssociation(s), Put/GetResolverQueryLogConfigPolicy |
| Resolver & DNSSEC configs | Get/Update/ListResolverConfig(s), Get/Update/ListResolverDnssecConfig(s) |
| DNS Firewall — domain lists | Create/Get/Delete/ListFirewallDomainList(s), Update/Import/ListFirewallDomains |
| DNS Firewall — rules | Create/Update/Delete/ListFirewallRule(s), BatchCreate/BatchUpdate/BatchDeleteFirewallRule |
| DNS Firewall — rule groups | Create/Get/Delete/ListFirewallRuleGroup(s), Put/GetFirewallRuleGroupPolicy |
| DNS Firewall — associations | Associate/Disassociate/Get/Update/ListFirewallRuleGroupAssociation(s) |
| DNS Firewall — configs | Get/Update/ListFirewallConfig(s), ListFirewallRuleTypes |
| Outpost resolvers | Create/Get/Update/Delete/ListOutpostResolver(s) |
| Tagging | TagResource, UntagResource, ListTagsForResource |

*Accepted but not simulated* (stored/echoed so SDK calls succeed, no behavioral
effect): endpoint/rule/config status stays terminal (no async CREATING→OPERATIONAL
transitions); `ImportFirewallDomains` records the request without fetching the S3
file; `ListFirewallRuleTypes` returns an empty descriptor list; resource-share
policies are stored verbatim without RAM enforcement.

**Total: 72 operations.**

---

## Provider-specific resources

Resources below are served for one provider only, because the concept exists in
Expand Down Expand Up @@ -2330,7 +2372,8 @@ still sees success.
| Machine Learning — GCP Vertex AI (Go API/driver) | 128 |
| AI Search — Azure AI Search (control + data plane) | 53 |
| Container Orchestration — AWS ECS | 37 |
| **Grand Total** | **1562** (+138 optional) |
| DNS Resolver — AWS Route 53 Resolver | 72 |
| **Grand Total** | **1634** (+138 optional) |

Optional operations are capabilities a driver may implement but is not required
to; see the sections marked "optional capability". They are counted separately
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/resourceexplorer2 v1.23.6
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.0
github.com/aws/aws-sdk-go-v2/service/route53resolver v1.48.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.254.0
github.com/aws/aws-sdk-go-v2/service/sagemakerfeaturestoreruntime v1.35.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12 h1:kOX5fC
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12/go.mod h1:n8ixkV2383DfuJhsCMVdfeSfYWqJhO2uadau9wrta9U=
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.0 h1:AYtTCOexiOMbe6Ier86t7Jfc8191htzChnNyg027PMo=
github.com/aws/aws-sdk-go-v2/service/route53 v1.64.0/go.mod h1:0hIRXFez1bZsDFMGkLZvNJbByTSVZ4sFZWpxZ39NPuM=
github.com/aws/aws-sdk-go-v2/service/route53resolver v1.48.3 h1:ZpybjxxYIArfRTBB+9yG9EEs7b4on+bjpWnUKFSWasw=
github.com/aws/aws-sdk-go-v2/service/route53resolver v1.48.3/go.mod h1:BTVlVIHKi7IiZkv8oam4lEClsIfrh08avL5V5UaQQco=
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.254.0 h1:0vYBf7g+R421AjrPAKh+zoNDhWxqg4KixviLFTQ+6vI=
Expand Down
3 changes: 3 additions & 0 deletions providers/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/stackshy/cloudemu/v2/providers/aws/rds"
"github.com/stackshy/cloudemu/v2/providers/aws/redshift"
"github.com/stackshy/cloudemu/v2/providers/aws/route53"
"github.com/stackshy/cloudemu/v2/providers/aws/route53resolver"
"github.com/stackshy/cloudemu/v2/providers/aws/s3"
"github.com/stackshy/cloudemu/v2/providers/aws/sagemaker"
"github.com/stackshy/cloudemu/v2/providers/aws/secretsmanager"
Expand Down Expand Up @@ -144,6 +145,7 @@ type Provider struct {
SageMaker *sagemaker.Mock
SSM *ssm.Mock
ECS *ecs.Mock
Route53Resolver *route53resolver.Mock
ResourceDiscovery *resourcediscovery.Engine
AccountID string
Region string
Expand Down Expand Up @@ -181,6 +183,7 @@ func New(opts ...config.Option) *Provider {
SageMaker: sagemaker.New(o),
SSM: ssm.New(o),
ECS: ecs.New(o),
Route53Resolver: route53resolver.New(o),
AccountID: o.AccountID,
Region: o.Region,
}
Expand Down
168 changes: 168 additions & 0 deletions providers/aws/route53resolver/configs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package route53resolver

import (
"context"

"github.com/stackshy/cloudemu/v2/internal/idgen"
"github.com/stackshy/cloudemu/v2/services/route53resolver/driver"
)

const (
autodefinedReverseEnabled = "ENABLED"
autodefinedReverseDisabled = "DISABLED"
autodefinedReverseLocal = "USE_LOCAL_RESOURCE_SETTING"

dnssecStatusEnabled = "ENABLED"
dnssecStatusDisabled = "DISABLED"
dnssecStatusLocal = "USE_LOCAL_RESOURCE_SETTING"

flagEnable = "ENABLE"
flagLocal = "USE_LOCAL_RESOURCE_SETTING"
)

func cloneResolverConfig(c *driver.ResolverConfig) driver.ResolverConfig { return *c }

func cloneDnssecConfig(c *driver.ResolverDnssecConfig) driver.ResolverDnssecConfig { return *c }

// autodefinedReverseFor maps a request flag to a stored autodefined-reverse
// status. Autodefined reverse-DNS rules are enabled by default in AWS.
func autodefinedReverseFor(flag string) string {
switch flag {
case flagEnable:
return autodefinedReverseEnabled
case flagLocal:
return autodefinedReverseLocal
default:
return autodefinedReverseDisabled
}
}

// dnssecStatusFor maps a request validation value to a stored DNSSEC status.
// DNSSEC validation is disabled by default in AWS.
func dnssecStatusFor(validation string) string {
switch validation {
case flagEnable:
return dnssecStatusEnabled
case flagLocal:
return dnssecStatusLocal
default:
return dnssecStatusDisabled
}
}

// resolverConfigFor returns the stored config for a VPC, materializing a
// default (autodefined reverse enabled) one on first access. Caller holds m.mu.
func (m *Mock) resolverConfigFor(resourceID string) *driver.ResolverConfig {
if c, ok := m.rslvrConfigs.Get(resourceID); ok {
return c
}

c := &driver.ResolverConfig{
ID: idgen.GenerateID("rslvr-rc-"),
OwnerID: m.opts.AccountID,
ResourceID: resourceID,
AutodefinedReverse: autodefinedReverseEnabled,
}
m.rslvrConfigs.Set(resourceID, c)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolverConfigFor does no validation and keys by the raw string, so GetResolverConfig("") (or any bogus id) mints and persists a default config that then appears in ListResolverConfigs forever. Same for dnssecConfigFor / firewallConfigFor. Validate the VpcId, or don't persist on a pure read.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. GetResolverConfig/GetResolverDnssecConfig/GetFirewallConfig now return the AWS default without persisting; only Update* materializes a stored record. A pure Get on a bogus VpcId no longer pollutes the List. Updated TestResolverConfigLazyDefaultAndUpdate to assert this.


return c
}

// dnssecConfigFor returns the stored DNSSEC config for a VPC, materializing a
// default (validation disabled) one on first access. Caller holds m.mu.
func (m *Mock) dnssecConfigFor(resourceID string) *driver.ResolverDnssecConfig {
if c, ok := m.dnssecCfgs.Get(resourceID); ok {
return c
}

c := &driver.ResolverDnssecConfig{
ID: idgen.GenerateID("rslvr-ds-"),
OwnerID: m.opts.AccountID,
ResourceID: resourceID,
ValidationStatus: dnssecStatusDisabled,
}
m.dnssecCfgs.Set(resourceID, c)

return c
}

func (m *Mock) GetResolverConfig(_ context.Context, resourceID string) (*driver.ResolverConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

if c, ok := m.rslvrConfigs.Get(resourceID); ok {
out := cloneResolverConfig(c)

return &out, nil
}

// A pure read never persists: return the AWS default (autodefined reverse
// enabled) without materializing a record that would then pollute the List.
return &driver.ResolverConfig{
OwnerID: m.opts.AccountID,
ResourceID: resourceID,
AutodefinedReverse: autodefinedReverseEnabled,
}, nil
}

func (m *Mock) UpdateResolverConfig(
_ context.Context, resourceID, autodefinedReverseFlag string,
) (*driver.ResolverConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

c := m.resolverConfigFor(resourceID)
c.AutodefinedReverse = autodefinedReverseFor(autodefinedReverseFlag)

out := cloneResolverConfig(c)

return &out, nil
}

func (m *Mock) ListResolverConfigs(_ context.Context) ([]driver.ResolverConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

return sortedValues(m.rslvrConfigs.All(), cloneResolverConfig), nil
}

func (m *Mock) GetResolverDnssecConfig(
_ context.Context, resourceID string,
) (*driver.ResolverDnssecConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

if c, ok := m.dnssecCfgs.Get(resourceID); ok {
out := cloneDnssecConfig(c)

return &out, nil
}

// A pure read never persists: return the AWS default (validation disabled).
return &driver.ResolverDnssecConfig{
OwnerID: m.opts.AccountID,
ResourceID: resourceID,
ValidationStatus: dnssecStatusDisabled,
}, nil
}

func (m *Mock) UpdateResolverDnssecConfig(
_ context.Context, resourceID, validation string,
) (*driver.ResolverDnssecConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

c := m.dnssecConfigFor(resourceID)
c.ValidationStatus = dnssecStatusFor(validation)

out := cloneDnssecConfig(c)

return &out, nil
}

func (m *Mock) ListResolverDnssecConfigs(_ context.Context) ([]driver.ResolverDnssecConfig, error) {
m.mu.Lock()
defer m.mu.Unlock()

return sortedValues(m.dnssecCfgs.All(), cloneDnssecConfig), nil
}
Loading
Loading