diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fe465390..2c0141e0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -5,8 +5,6 @@ on: branches: - main pull_request: - branches: - - main jobs: e2e: diff --git a/.internal-dns-e2e/kubernetes/crds.yaml b/.internal-dns-e2e/kubernetes/crds.yaml new file mode 100644 index 00000000..00cbe9d7 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/crds.yaml @@ -0,0 +1,4219 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnscontributiongrants.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSContributionGrant + listKind: DNSContributionGrantList + plural: dnscontributiongrants + singular: dnscontributiongrant + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + nameScopes: + items: + type: string + type: array + principal: + properties: + subject: + minLength: 1 + type: string + required: + - subject + type: object + producerID: + type: string + recordTypes: + items: + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + type: array + registrationRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - principal + - producerID + - recordTypes + - registrationRef + type: object + x-kubernetes-validations: + - message: registrationRef requires name and UID + rule: self.registrationRef.name.size() > 0 && has(self.registrationRef.uid) + && self.registrationRef.uid.size() > 0 + status: + properties: + activeWriterEpoch: + format: int64 + type: integer + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGrantGeneration: + format: int64 + type: integer + observedRegistrationGeneration: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnsmanagednamespaces.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSManagedNamespace + listKind: DNSManagedNamespaceList + plural: dnsmanagednamespaces + singular: dnsmanagednamespace + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + DNSManagedNamespace is the DNS-owned lookup and lifecycle record for the + canonical private namespace of a VPC. Product controllers read its status; + only the DNS platform controller writes the object. + properties: + dnsZoneClassName: + type: string + domainSuffix: + type: string + projectUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + vpcRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - dnsZoneClassName + - domainSuffix + - projectUID + - vpcRef + type: object + status: + properties: + associationRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + canonicalSuffix: + type: string + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnsnamingpolicies.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSNamingPolicy + listKind: DNSNamingPolicyList + plural: dnsnamingpolicies + singular: dnsnamingpolicy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + additionalNames: + items: + properties: + dnsZoneRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + namePrefix: + type: string + selector: + description: |- + Selector matches DNSRegistration metadata labels within this DNS context. + An omitted or empty selector matches every registration in the context. + properties: + matchExpressions: + items: + properties: + key: + maxLength: 317 + minLength: 1 + type: string + operator: + description: A label selector operator is the set + of operators that can be used in a selector requirement. + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + items: + maxLength: 63 + pattern: ^([A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$ + type: string + maxItems: 64 + type: array + required: + - key + - operator + type: object + x-kubernetes-validations: + - message: selector operator requires compatible values + rule: '(self.operator in [''In'', ''NotIn'']) ? (has(self.values) + && self.values.size() > 0) : (!has(self.values) || + self.values.size() == 0)' + maxItems: 64 + type: array + matchLabels: + additionalProperties: + type: string + maxProperties: 64 + type: object + type: object + required: + - dnsZoneRef + - namePrefix + type: object + x-kubernetes-validations: + - message: dnsZoneRef requires a name + rule: self.dnsZoneRef.name.size() > 0 + maxItems: 256 + type: array + dnsZoneRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + priority: + format: int32 + type: integer + resolverContextRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + scopePrefix: + type: string + vpcRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + required: + - additionalNames + type: object + x-kubernetes-validations: + - message: exactly one named vpcRef or resolverContextRef is required + rule: (has(self.vpcRef) && has(self.vpcRef.name) && self.vpcRef.name.size() + > 0) != (has(self.resolverContextRef) && has(self.resolverContextRef.name) + && self.resolverContextRef.name.size() > 0) + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + resolvedAdditionalNames: + items: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + namePrefix: + type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - dnsZoneRef + - namePrefix + type: object + type: array + resolvedDNSZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedResolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedVPCRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnspublicationchunks.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSPublicationChunk + listKind: DNSPublicationChunkList + plural: dnspublicationchunks + singular: dnspublicationchunk + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + index: + format: int32 + minimum: 0 + type: integer + manifestUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + payload: + format: byte + minLength: 1 + type: string + revision: + format: int64 + minimum: 1 + type: integer + sha256: + pattern: ^[a-fA-F0-9]{64}$ + type: string + writerEpoch: + format: int64 + minimum: 1 + type: integer + required: + - index + - payload + - revision + - sha256 + - writerEpoch + type: object + x-kubernetes-validations: + - message: publication chunks are immutable + rule: oldSelf == self + required: + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnspublicationmanifests.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSPublicationManifest + listKind: DNSPublicationManifestList + plural: dnspublicationmanifests + singular: dnspublicationmanifest + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + chunks: + items: + properties: + name: + minLength: 1 + type: string + sha256: + pattern: ^[a-fA-F0-9]{64}$ + type: string + size: + format: int32 + minimum: 1 + type: integer + required: + - name + - sha256 + - size + type: object + type: array + contentHash: + pattern: ^[a-fA-F0-9]{64}$ + type: string + contextUIDs: + items: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + type: array + contributionFences: + items: + description: |- + DNSContributionFence persists an authenticated high-water sequence even + while its observation is unhealthy or withdrawn. + properties: + epoch: + format: int64 + type: integer + grantUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + sequence: + format: int64 + type: integer + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + required: + - epoch + - grantUID + - sequence + - uid + type: object + type: array + generatedAt: + format: date-time + type: string + previousRevision: + format: int64 + type: integer + registrationFences: + items: + properties: + generation: + format: int64 + type: integer + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + required: + - generation + - uid + type: object + type: array + revision: + format: int64 + minimum: 1 + type: integer + servingTargets: + items: + properties: + region: + type: string + shard: + type: string + required: + - region + - shard + type: object + type: array + tombstone: + type: boolean + writerEpoch: + format: int64 + minimum: 1 + type: integer + zoneApex: + minLength: 1 + type: string + zoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - chunks + - contentHash + - contextUIDs + - generatedAt + - revision + - writerEpoch + - zoneApex + - zoneRef + type: object + x-kubernetes-validations: + - message: publication manifests are immutable + rule: oldSelf == self + - message: zoneRef requires name and UID + rule: self.zoneRef.name.size() > 0 && has(self.zoneRef.uid) && self.zoneRef.uid.size() + > 0 + - message: previousRevision must precede revision + rule: '!has(self.previousRevision) || (self.previousRevision >= 0 && + self.previousRevision < self.revision)' + - message: withdrawal requires empty content; publication requires chunks + rule: '(has(self.tombstone) && self.tombstone) ? (self.chunks.size() + == 0 && self.contentHash == ''e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'') + : self.chunks.size() > 0' + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + exportAttempts: + format: int32 + type: integer + exportState: + type: string + exportedAt: + format: date-time + type: string + replicaAcknowledgements: + items: + properties: + authorizationIssuerEpoch: + format: int64 + type: integer + authorizationRevision: + format: int64 + type: integer + error: + type: string + memberID: + type: string + observedAt: + format: date-time + type: string + observedUnixNano: + format: int64 + type: integer + phase: + type: string + revision: + format: int64 + type: integer + snapshotRevision: + format: int64 + type: integer + validUntil: + format: date-time + type: string + writerEpoch: + format: int64 + type: integer + required: + - memberID + - observedAt + - phase + - revision + type: object + type: array + streamSequence: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnspublicationownerships.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSPublicationOwnership + listKind: DNSPublicationOwnershipList + plural: dnspublicationownerships + singular: dnspublicationownership + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + DNSPublicationOwnership is a durable compare-and-swap record for a zone + compiler. A lease takeover increments WriterEpoch and fences delayed holders. + properties: + activeManifestName: + type: string + holderIdentity: + maxLength: 512 + minLength: 1 + type: string + lastContentHash: + type: string + leaseUntil: + format: date-time + type: string + nextRevision: + format: int64 + minimum: 1 + type: integer + writerEpoch: + format: int64 + minimum: 1 + type: integer + zoneUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + minLength: 1 + type: string + x-kubernetes-validations: + - message: zoneUID is immutable + rule: self == oldSelf + required: + - holderIdentity + - leaseUntil + - nextRevision + - writerEpoch + - zoneUID + type: object + x-kubernetes-validations: + - message: publication ownership fences must not decrease + rule: self.writerEpoch >= oldSelf.writerEpoch && self.nextRevision >= + oldSelf.nextRevision + - message: ownership transfer requires a new writer epoch + rule: self.holderIdentity == oldSelf.holderIdentity || self.writerEpoch + > oldSelf.writerEpoch + required: + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsrecordcontributions.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSRecordContribution + listKind: DNSRecordContributionList + plural: dnsrecordcontributions + singular: dnsrecordcontribution + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + grantRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + recordSets: + items: + properties: + recordType: + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + records: + items: + description: RecordEntry represents one owner name and its + values. + properties: + a: + description: Exactly one of the following type-specific + fields should be set matching RecordType. + properties: + content: + format: ipv4 + type: string + required: + - content + type: object + aaaa: + properties: + content: + format: ipv6 + type: string + required: + - content + type: object + alias: + description: |- + ALIASRecordSpec is like CNAME but intended for providers that implement an ALIAS/ANAME-style record. + The content is a hostname (FQDN or relative) with an optional trailing dot. + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + caa: + properties: + flag: + description: 0–255 flag + maximum: 255 + minimum: 0 + type: integer + tag: + description: 'RFC-style tags: keep it simple: [a-z0-9]+' + minLength: 1 + pattern: ^[a-z0-9]+$ + type: string + value: + minLength: 1 + type: string + required: + - flag + - tag + - value + type: object + cname: + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + https: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + mx: + properties: + exchange: + minLength: 1 + type: string + preference: + maximum: 65535 + minimum: 0 + type: integer + required: + - exchange + - preference + type: object + name: + description: Name is the owner name (relative to the zone + or FQDN). + minLength: 1 + pattern: ^(@|[A-Za-z0-9*._-]+)$ + type: string + ns: + properties: + content: + description: |- + Require a hostname (FQDN or relative), allow optional trailing dot, no underscores. + Labels: 1-63 chars, alphanum with interior hyphens, total length <=253. + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*\.?$ + type: string + required: + - content + type: object + ptr: + properties: + content: + type: string + required: + - content + type: object + soa: + properties: + expire: + format: int32 + type: integer + mname: + minLength: 1 + type: string + refresh: + format: int32 + type: integer + retry: + format: int32 + type: integer + rname: + minLength: 1 + type: string + serial: + format: int32 + type: integer + ttl: + format: int32 + type: integer + required: + - mname + - rname + type: object + srv: + properties: + port: + maximum: 65535 + minimum: 0 + type: integer + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + minLength: 1 + type: string + weight: + maximum: 65535 + minimum: 0 + type: integer + required: + - port + - priority + - target + - weight + type: object + svcb: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + tlsa: + properties: + certData: + type: string + matchingType: + type: integer + selector: + type: integer + usage: + type: integer + required: + - certData + - matchingType + - selector + - usage + type: object + ttl: + description: TTL optionally overrides TTL for this owner/RRset. + format: int64 + type: integer + txt: + properties: + content: + type: string + required: + - content + type: object + required: + - name + type: object + type: array + required: + - recordType + - records + type: object + type: array + registrationRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - grantRef + - recordSets + - registrationRef + type: object + x-kubernetes-validations: + - message: registrationRef requires name, UID, and generation + rule: self.registrationRef.name.size() > 0 && has(self.registrationRef.uid) + && self.registrationRef.uid.size() > 0 && has(self.registrationRef.generation) + && self.registrationRef.generation > 0 + - message: grantRef requires name and UID + rule: self.grantRef.name.size() > 0 && has(self.grantRef.uid) && self.grantRef.uid.size() + > 0 + - message: contribution authority references are immutable + rule: self.registrationRef == oldSelf.registrationRef && self.grantRef + == oldSelf.grantRef + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + eligible: + type: boolean + observedGeneration: + format: int64 + type: integer + publishedRevision: + format: int64 + type: integer + reason: + type: string + sequence: + format: int64 + type: integer + writerEpoch: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsrecordsets.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSRecordSet + listKind: DNSRecordSetList + plural: dnsrecordsets + singular: dnsrecordset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: DNSRecordSet is the Schema for the dnsrecordsets API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of DNSRecordSet + properties: + dnsZoneRef: + description: DNSZoneRef references the DNSZone (same namespace) this + recordset belongs to. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: dnsZoneRef.name must be set + rule: self.name != '' + recordType: + description: RecordType is the DNS RR type for this recordset. + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + records: + description: Records contains one or more owner names with values + appropriate for the RecordType. + items: + description: RecordEntry represents one owner name and its values. + properties: + a: + description: Exactly one of the following type-specific fields + should be set matching RecordType. + properties: + content: + format: ipv4 + type: string + required: + - content + type: object + aaaa: + properties: + content: + format: ipv6 + type: string + required: + - content + type: object + alias: + description: |- + ALIASRecordSpec is like CNAME but intended for providers that implement an ALIAS/ANAME-style record. + The content is a hostname (FQDN or relative) with an optional trailing dot. + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + caa: + properties: + flag: + description: 0–255 flag + maximum: 255 + minimum: 0 + type: integer + tag: + description: 'RFC-style tags: keep it simple: [a-z0-9]+' + minLength: 1 + pattern: ^[a-z0-9]+$ + type: string + value: + minLength: 1 + type: string + required: + - flag + - tag + - value + type: object + cname: + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + https: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + mx: + properties: + exchange: + minLength: 1 + type: string + preference: + maximum: 65535 + minimum: 0 + type: integer + required: + - exchange + - preference + type: object + name: + description: Name is the owner name (relative to the zone or + FQDN). + minLength: 1 + pattern: ^(@|[A-Za-z0-9*._-]+)$ + type: string + ns: + properties: + content: + description: |- + Require a hostname (FQDN or relative), allow optional trailing dot, no underscores. + Labels: 1-63 chars, alphanum with interior hyphens, total length <=253. + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*\.?$ + type: string + required: + - content + type: object + ptr: + properties: + content: + type: string + required: + - content + type: object + soa: + properties: + expire: + format: int32 + type: integer + mname: + minLength: 1 + type: string + refresh: + format: int32 + type: integer + retry: + format: int32 + type: integer + rname: + minLength: 1 + type: string + serial: + format: int32 + type: integer + ttl: + format: int32 + type: integer + required: + - mname + - rname + type: object + srv: + properties: + port: + maximum: 65535 + minimum: 0 + type: integer + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + minLength: 1 + type: string + weight: + maximum: 65535 + minimum: 0 + type: integer + required: + - port + - priority + - target + - weight + type: object + svcb: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + tlsa: + properties: + certData: + type: string + matchingType: + type: integer + selector: + type: integer + usage: + type: integer + required: + - certData + - matchingType + - selector + - usage + type: object + ttl: + description: TTL optionally overrides TTL for this owner/RRset. + format: int64 + type: integer + txt: + properties: + content: + type: string + required: + - content + type: object + required: + - name + type: object + minItems: 1 + type: array + required: + - dnsZoneRef + - recordType + - records + type: object + status: + description: status defines the observed state of DNSRecordSet + properties: + conditions: + description: Conditions includes Accepted and Programmed readiness. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + recordSets: + description: RecordSets captures per-owner (per name) status and conditions. + items: + properties: + conditions: + description: Conditions captures per-name readiness information + such as RecordProgrammed. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the owner name this status pertains to. + type: string + required: + - name + type: object + type: array + type: object + required: + - spec + type: object + selectableFields: + - jsonPath: .spec.dnsZoneRef.name + - jsonPath: .spec.recordType + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsregistrations.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSRegistration + listKind: DNSRegistrationList + plural: dnsregistrations + singular: dnsregistration + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + name: + type: string + publicationPolicy: + enum: + - EligibleContributions + - Persistent + type: string + recordTypes: + items: + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + type: array + reservedDescendants: + items: + type: string + type: array + ttlSeconds: + description: TTLSeconds bounds cached dynamic answers to at most 30 + seconds after withdrawal. + format: int32 + maximum: 30 + minimum: 1 + type: integer + required: + - dnsZoneRef + - name + - publicationPolicy + - recordTypes + - ttlSeconds + type: object + x-kubernetes-validations: + - message: dnsZoneRef must pin the zone UID + rule: has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0 + status: + properties: + canonicalFQDN: + type: string + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + publicationRevision: + format: int64 + type: integer + publicationWriterEpoch: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsresolveraccessbindings.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSResolverAccessBinding + listKind: DNSResolverAccessBindingList + plural: dnsresolveraccessbindings + singular: dnsresolveraccessbinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + authorization: + properties: + sequence: + format: int64 + minimum: 1 + type: integer + writerEpoch: + format: int64 + minimum: 1 + type: integer + required: + - sequence + - writerEpoch + type: object + contextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + port: + enum: + - 53 + format: int32 + type: integer + queryIdentity: + properties: + type: + enum: + - DestinationAddress + type: string + value: + maxLength: 253 + minLength: 1 + type: string + required: + - type + - value + type: object + region: + maxLength: 253 + minLength: 1 + type: string + transports: + items: + enum: + - UDP + - TCP + type: string + maxItems: 2 + minItems: 2 + type: array + x-kubernetes-list-type: set + required: + - authorization + - contextRef + - port + - queryIdentity + - region + - transports + type: object + x-kubernetes-validations: + - message: contextRef requires name and UID + rule: self.contextRef.name.size() > 0 && has(self.contextRef.uid) && + self.contextRef.uid.size() > 0 + - message: contextRef is immutable + rule: self.contextRef == oldSelf.contextRef + - message: access identity and transport are immutable + rule: self.region == oldSelf.region && self.queryIdentity == oldSelf.queryIdentity + && self.port == oldSelf.port && self.transports == oldSelf.transports + - message: writerEpoch is immutable for an access binding + rule: self.authorization.writerEpoch == oldSelf.authorization.writerEpoch + - message: authorization changes require an increasing sequence + rule: self.authorization == oldSelf.authorization || self.authorization.sequence + > oldSelf.authorization.sequence + status: + properties: + bindingRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedSequence: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnsresolverbindings.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSResolverBinding + listKind: DNSResolverBindingList + plural: dnsresolverbindings + singular: dnsresolverbinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DNSResolverBinding is the DNS-service projection of a consumer + context and access binding. + properties: + authorization: + properties: + sequence: + format: int64 + minimum: 1 + type: integer + writerEpoch: + format: int64 + minimum: 1 + type: integer + required: + - sequence + - writerEpoch + type: object + configuration: + properties: + generation: + format: int64 + minimum: 1 + type: integer + listeners: + properties: + node: + properties: + address: + maxLength: 253 + minLength: 1 + type: string + port: + enum: + - 53 + format: int32 + type: integer + transports: + items: + enum: + - UDP + - TCP + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + required: + - address + - port + - transports + type: object + regional: + properties: + address: + maxLength: 253 + minLength: 1 + type: string + port: + enum: + - 53 + format: int32 + type: integer + transports: + items: + enum: + - UDP + - TCP + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + required: + - address + - port + - transports + type: object + required: + - node + - regional + type: object + x-kubernetes-validations: + - message: listener identity is immutable for an access binding + rule: oldSelf == self + revision: + format: int64 + minimum: 1 + type: integer + zoneRefs: + items: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + maxItems: 256 + type: array + required: + - generation + - listeners + - revision + - zoneRefs + type: object + x-kubernetes-validations: + - message: zone references must pin name and UID + rule: self.zoneRefs.all(z, z.name.size() > 0 && has(z.uid) && z.uid.size() + > 0) + placement: + properties: + region: + maxLength: 253 + minLength: 1 + type: string + shard: + maxLength: 253 + minLength: 1 + type: string + required: + - region + - shard + type: object + x-kubernetes-validations: + - message: binding placement is immutable + rule: oldSelf == self + source: + properties: + accessBindingRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + projectUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + resolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - accessBindingRef + - projectUID + - resolverContextRef + type: object + x-kubernetes-validations: + - message: binding source identity is immutable + rule: oldSelf == self + - message: binding source requires project UID and UID-pinned context + and access references + rule: self.projectUID.size() > 0 && self.resolverContextRef.name.size() + > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() + > 0 && self.accessBindingRef.name.size() > 0 && has(self.accessBindingRef.uid) + && self.accessBindingRef.uid.size() > 0 + tombstone: + type: boolean + required: + - authorization + - configuration + - placement + - source + type: object + x-kubernetes-validations: + - message: configuration fences must not decrease + rule: self.configuration.generation >= oldSelf.configuration.generation + && self.configuration.revision >= oldSelf.configuration.revision + - message: authorization fences must not decrease + rule: self.authorization.writerEpoch > oldSelf.authorization.writerEpoch + || (self.authorization.writerEpoch == oldSelf.authorization.writerEpoch + && self.authorization.sequence >= oldSelf.authorization.sequence) + - message: zone membership changes require a new cache generation and + configuration revision + rule: self.configuration.zoneRefs == oldSelf.configuration.zoneRefs + || (self.configuration.generation > oldSelf.configuration.generation + && self.configuration.revision > oldSelf.configuration.revision) + - message: changing withdrawal state requires a new configuration revision + rule: (has(self.tombstone) && self.tombstone) == (has(oldSelf.tombstone) + && oldSelf.tombstone) || self.configuration.revision > oldSelf.configuration.revision + - message: reactivating withdrawn access requires a new source authorization + fence + rule: '!has(oldSelf.tombstone) || !oldSelf.tombstone || (has(self.tombstone) + && self.tombstone) || self.authorization.writerEpoch > oldSelf.authorization.writerEpoch + || (self.authorization.writerEpoch == oldSelf.authorization.writerEpoch + && self.authorization.sequence > oldSelf.authorization.sequence)' + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + memberAcknowledgements: + items: + properties: + authorizationIssuerEpoch: + format: int64 + type: integer + authorizationRevision: + format: int64 + type: integer + error: + type: string + memberID: + type: string + observedAt: + format: date-time + type: string + observedUnixNano: + format: int64 + type: integer + phase: + type: string + revision: + format: int64 + type: integer + snapshotRevision: + format: int64 + type: integer + validUntil: + format: date-time + type: string + writerEpoch: + format: int64 + type: integer + required: + - memberID + - observedAt + - phase + - revision + type: object + type: array + observedConfigurationRevision: + format: int64 + type: integer + phase: + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsresolvercontexts.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSResolverContext + listKind: DNSResolverContextList + plural: dnsresolvercontexts + singular: dnsresolvercontext + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + DNSResolverContext is the DNS-owned serving and namespace boundary created by + a trusted network integration. consumerID is opaque to DNS and immutable. + properties: + consumerID: + maxLength: 512 + minLength: 1 + type: string + managedNamespace: + properties: + domainSuffix: + description: DomainSuffix is the zone apex requested by the trusted + consumer integration. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$ + type: string + enabled: + type: boolean + type: object + x-kubernetes-validations: + - message: domainSuffix is required when the managed namespace is + enabled + rule: '!has(self.enabled) || !self.enabled || (has(self.domainSuffix) + && self.domainSuffix.size() > 0)' + required: + - consumerID + type: object + x-kubernetes-validations: + - message: consumerID is immutable + rule: oldSelf.consumerID == self.consumerID + - message: managed namespace domainSuffix is immutable + rule: '!has(oldSelf.managedNamespace) || !has(oldSelf.managedNamespace.domainSuffix) + || (has(self.managedNamespace) && has(self.managedNamespace.domainSuffix) + && self.managedNamespace.domainSuffix == oldSelf.managedNamespace.domainSuffix)' + status: + properties: + accessWriterEpoch: + format: int64 + type: integer + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + managedNamespace: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + suffix: + type: string + type: object + servingTargets: + items: + properties: + region: + type: string + shard: + type: string + required: + - region + - shard + type: object + type: array + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnstransportoutboxes.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSTransportOutbox + listKind: DNSTransportOutboxList + plural: dnstransportoutboxes + singular: dnstransportoutbox + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + activation: + type: boolean + dependsOn: + items: + type: string + type: array + manifestRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + ownershipRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + payload: + format: byte + type: string + payloadHash: + type: string + resourceUID: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + revision: + format: int64 + type: integer + subject: + type: string + writerEpoch: + format: int64 + type: integer + required: + - manifestRef + - payload + - payloadHash + - resourceUID + - revision + - subject + - writerEpoch + type: object + x-kubernetes-validations: + - message: outbox entries are immutable + rule: oldSelf == self + status: + properties: + acknowledgedAt: + format: date-time + type: string + attempts: + format: int32 + type: integer + lastError: + type: string + state: + type: string + streamSequence: + format: int64 + type: integer + type: object + required: + - spec + type: object + selectableFields: + - jsonPath: .status.state + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnszoneassociations.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSZoneAssociation + listKind: DNSZoneAssociationList + plural: dnszoneassociations + singular: dnszoneassociation + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + dnsZoneRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + managed: + type: boolean + resolverContextRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + vpcRef: + description: |- + DNSNameReference selects a resource in the same project namespace. + DNS records its resolved lifetime in controller-owned status. + properties: + name: + maxLength: 253 + type: string + required: + - name + type: object + required: + - dnsZoneRef + type: object + x-kubernetes-validations: + - message: exactly one named vpcRef or resolverContextRef is required + rule: (has(self.vpcRef) && has(self.vpcRef.name) && self.vpcRef.name.size() + > 0) != (has(self.resolverContextRef) && has(self.resolverContextRef.name) + && self.resolverContextRef.name.size() > 0) + - message: dnsZoneRef requires a name + rule: self.dnsZoneRef.name.size() > 0 + status: + properties: + bindingRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + resolvedDNSZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedResolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedVPCRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnszoneclasses.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSZoneClass + listKind: DNSZoneClassList + plural: dnszoneclasses + singular: dnszoneclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: DNSZoneClass is the Schema for the dnszoneclasses API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of DNSZoneClass + properties: + controllerName: + description: |- + ControllerName names the service that provisions and serves zones of this + class (for example, "powerdns"). + type: string + defaults: + description: Defaults provides optional default values applied to + managed zones. + properties: + defaultTTL: + description: DefaultTTL is the default TTL applied to records + when not otherwise specified. + format: int64 + type: integer + type: object + nameServerPolicy: + description: NameServerPolicy defines how nameservers are assigned + for zones using this class. + properties: + mode: + description: Mode defines which policy to use. + enum: + - Static + type: string + static: + description: Static contains a static list of authoritative nameservers + when Mode == "Static". + properties: + servers: + items: + type: string + type: array + required: + - servers + type: object + required: + - mode + type: object + required: + - controllerName + type: object + status: + description: status defines the observed state of DNSZoneClass + properties: + conditions: + description: |- + Conditions represent the current state of the resource. Common types include + "Accepted" and "Programmed" to standardize readiness reporting across controllers. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnszonediscoveries.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSZoneDiscovery + listKind: DNSZoneDiscoveryList + plural: dnszonediscoveries + shortNames: + - dnszd + singular: dnszonediscovery + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Discovered")].status + name: Discovered + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: DNSZoneDiscovery is the Schema for the DNSZone discovery API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired target for discovery. + properties: + dnsZoneRef: + description: DNSZoneRef references the DNSZone (same namespace) this + discovery targets. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - dnsZoneRef + type: object + status: + description: status contains the discovered data (write-once). + properties: + conditions: + description: Conditions includes Accepted and Discovered. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + recordSets: + description: RecordSets is the set of discovered RRsets grouped by + RecordType. + items: + description: DiscoveredRecordSet groups discovered records by type. + properties: + recordType: + description: RecordType is the DNS RR type for this recordset. + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + records: + description: |- + Records contains one or more owner names with values appropriate for the RecordType. + The RecordEntry schema is shared with DNSRecordSet for easy translation. + items: + description: RecordEntry represents one owner name and its + values. + properties: + a: + description: Exactly one of the following type-specific + fields should be set matching RecordType. + properties: + content: + format: ipv4 + type: string + required: + - content + type: object + aaaa: + properties: + content: + format: ipv6 + type: string + required: + - content + type: object + alias: + description: |- + ALIASRecordSpec is like CNAME but intended for providers that implement an ALIAS/ANAME-style record. + The content is a hostname (FQDN or relative) with an optional trailing dot. + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + caa: + properties: + flag: + description: 0–255 flag + maximum: 255 + minimum: 0 + type: integer + tag: + description: 'RFC-style tags: keep it simple: [a-z0-9]+' + minLength: 1 + pattern: ^[a-z0-9]+$ + type: string + value: + minLength: 1 + type: string + required: + - flag + - tag + - value + type: object + cname: + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + https: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + mx: + properties: + exchange: + minLength: 1 + type: string + preference: + maximum: 65535 + minimum: 0 + type: integer + required: + - exchange + - preference + type: object + name: + description: Name is the owner name (relative to the zone + or FQDN). + minLength: 1 + pattern: ^(@|[A-Za-z0-9*._-]+)$ + type: string + ns: + properties: + content: + description: |- + Require a hostname (FQDN or relative), allow optional trailing dot, no underscores. + Labels: 1-63 chars, alphanum with interior hyphens, total length <=253. + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*\.?$ + type: string + required: + - content + type: object + ptr: + properties: + content: + type: string + required: + - content + type: object + soa: + properties: + expire: + format: int32 + type: integer + mname: + minLength: 1 + type: string + refresh: + format: int32 + type: integer + retry: + format: int32 + type: integer + rname: + minLength: 1 + type: string + serial: + format: int32 + type: integer + ttl: + format: int32 + type: integer + required: + - mname + - rname + type: object + srv: + properties: + port: + maximum: 65535 + minimum: 0 + type: integer + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + minLength: 1 + type: string + weight: + maximum: 65535 + minimum: 0 + type: integer + required: + - port + - priority + - target + - weight + type: object + svcb: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + tlsa: + properties: + certData: + type: string + matchingType: + type: integer + selector: + type: integer + usage: + type: integer + required: + - certData + - matchingType + - selector + - usage + type: object + ttl: + description: TTL optionally overrides TTL for this owner/RRset. + format: int64 + type: integer + txt: + properties: + content: + type: string + required: + - content + type: object + required: + - name + type: object + type: array + required: + - recordType + - records + type: object + type: array + type: object + required: + - spec + type: object + selectableFields: + - jsonPath: .spec.dnsZoneRef.name + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnszones.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSZone + listKind: DNSZoneList + plural: dnszones + singular: dnszone + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .status.recordCount + name: Records + type: integer + name: v1alpha1 + schema: + openAPIV3Schema: + description: DNSZone is the Schema for the dnszones API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of DNSZone + properties: + dnsZoneClassName: + description: DNSZoneClassName references the DNSZoneClass used to + provision this zone. + type: string + domainName: + description: DomainName is the FQDN of the zone (e.g., "example.com"). + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + x-kubernetes-validations: + - message: A domain name is immutable and cannot be changed after + creation + rule: oldSelf == '' || self == oldSelf + - message: Must have at least two segments separated by dots + rule: self.indexOf('.') != -1 + visibility: + default: Public + description: |- + Visibility selects public publication or private resolver context serving. + Existing zones default to Public. Changing visibility requires a new zone. + enum: + - Public + - Private + type: string + x-kubernetes-validations: + - message: visibility is immutable + rule: oldSelf == self + required: + - dnsZoneClassName + - domainName + type: object + status: + description: status defines the observed state of DNSZone + properties: + conditions: + description: Conditions tracks state such as Accepted and Programmed + readiness. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + domainRef: + description: DomainRef references the Domain this zone belongs to. + properties: + name: + type: string + status: + properties: + nameservers: + items: + properties: + hostname: + type: string + ips: + items: + description: NameserverIP captures per-address provenance + for a nameserver. + properties: + address: + type: string + registrantName: + type: string + required: + - address + type: object + type: array + required: + - hostname + type: object + type: array + type: object + required: + - name + type: object + nameservers: + description: Nameservers lists the active authoritative nameservers + for this zone. + items: + type: string + type: array + recordCount: + description: RecordCount is the total number of DNS records in this + zone. + type: integer + type: object + required: + - spec + type: object + selectableFields: + - jsonPath: .spec.domainName + - jsonPath: .status.domainRef.name + served: true + storage: true + subresources: + status: {} diff --git a/.internal-dns-e2e/kubernetes/image.id b/.internal-dns-e2e/kubernetes/image.id new file mode 100644 index 00000000..a3d10dcc --- /dev/null +++ b/.internal-dns-e2e/kubernetes/image.id @@ -0,0 +1 @@ +sha256:5fc2d91b586be5a4bf3a19e695a002a67b2cda673fe992fc25ab6a402c420db8 \ No newline at end of file diff --git a/.internal-dns-e2e/kubernetes/kind-config.yaml b/.internal-dns-e2e/kubernetes/kind-config.yaml new file mode 100644 index 00000000..7ce1ca91 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/kind-config.yaml @@ -0,0 +1,39 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + # No extraPortMappings: NodePorts for Envoy Gateway/Grafana are allocated + # dynamically (see components/envoy-gateway-operator and + # components/observability), so multiple clusters can run concurrently on + # the same Docker host without colliding on fixed host ports. Reach + # services via `kubectl port-forward` or the kind Docker network. + + # Add audit log configurations + kubeadmConfigPatches: + - | + kind: ClusterConfiguration + apiServer: + extraArgs: + audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml + audit-log-path: /var/log/kubernetes/kube-apiserver-audit.log + extraVolumes: + - name: audit-policies + hostPath: /etc/kubernetes/policies + mountPath: /etc/kubernetes/policies + readOnly: true + pathType: "DirectoryOrCreate" + - name: "audit-logs" + hostPath: "/var/log/kubernetes" + mountPath: "/var/log/kubernetes" + readOnly: false + pathType: DirectoryOrCreate + # mount the local file on the control plane + extraMounts: + # The filepath is dynamically set via REPO_DIR environment variable by the Taskfile. + # When running in test-infra repo: ./cluster/audit-policy.yaml + # When running from external repo: ./.test-infra/cluster/audit-policy.yaml + - hostPath: /tmp/datum-internal-dns-audit/audit-policy.yaml + containerPath: /etc/kubernetes/policies/audit-policy.yaml + readOnly: true +networking: + ipFamily: dual diff --git a/.internal-dns-e2e/kubernetes/platform-bootstrap.json b/.internal-dns-e2e/kubernetes/platform-bootstrap.json new file mode 100644 index 00000000..6b185976 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/platform-bootstrap.json @@ -0,0 +1,2566 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "project-e2e" + } + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "automountServiceAccountToken": false, + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-fleet", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-vpc-integration", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status", + "dnsmanagednamespaces", + "dnsmanagednamespaces/status" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-product-writer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations", + "dnsrecordcontributions" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants", + "dnsresolvercontexts", + "dnsmanagednamespaces", + "dnsnamingpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsrecordcontributions/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-customer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsnamingpolicies", + "dnsregistrations" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-grant-issuer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "internal-dns-control-plane" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsregistrations", + "dnscontributiongrants", + "dnsrecordcontributions", + "dnsnamingpolicies", + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnszoneassociations" + ], + "verbs": [ + "create", + "patch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings", + "dnspublicationmanifests", + "dnspublicationchunks", + "dnstransportoutboxes", + "dnspublicationownerships", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings/status", + "dnspublicationmanifests/status", + "dnstransportoutboxes/status", + "dnspublicationownerships/status", + "dnsmanagednamespaces/status", + "dnszones/status", + "dnszoneassociations/status", + "dnsnamingpolicies/status", + "dnsregistrations/status", + "dnscontributiongrants/status", + "dnsrecordcontributions/status", + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "" + ], + "resources": [ + "configmaps" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "internal-dns-control-plane" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "internal-dns-control-plane" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Issuer", + "metadata": { + "name": "internal-dns-selfsigned", + "namespace": "internal-dns-system" + }, + "spec": { + "selfSigned": {} + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "internal-dns-ca", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "internal-dns-ca", + "duration": "24h", + "isCA": true, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-selfsigned" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "internal-dns-ca", + "usages": [ + "cert sign", + "crl sign" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Issuer", + "metadata": { + "name": "internal-dns-ca", + "namespace": "internal-dns-system" + }, + "spec": { + "ca": { + "secretName": "internal-dns-ca" + } + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "internal-dns-nats-server", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "internal-dns-nats-server", + "dnsNames": [ + "nats.internal-dns-system.svc", + "nats.internal-dns-system.svc.cluster.local", + "*.internal-dns-nats.internal-dns-system.svc.cluster.local" + ], + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "internal-dns-nats-server", + "usages": [ + "server auth", + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "internal-dns-admission-tls", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "internal-dns-admission-tls", + "dnsNames": [ + "internal-dns-admission.internal-dns-system.svc", + "dns-control-control-plane" + ], + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "internal-dns-admission-tls", + "usages": [ + "server auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "control-a-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "control-a-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "control-a-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "control-b-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "control-b-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "control-b-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "node-us-central1-0-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "node-us-central1-0-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "node-us-central1-0-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "regional-front-us-central1-0-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "regional-front-us-central1-0-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "regional-front-us-central1-0-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "regional-bind-us-central1-0-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "regional-bind-us-central1-0-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "regional-bind-us-central1-0-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "regional-bind-us-central1-1-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "commonName": "regional-bind-us-central1-1-nats", + "duration": "24h", + "isCA": false, + "issuerRef": { + "kind": "Issuer", + "name": "internal-dns-ca" + }, + "privateKey": { + "algorithm": "ECDSA", + "size": 256 + }, + "renewBefore": "8h", + "secretName": "regional-bind-us-central1-1-nats", + "usages": [ + "client auth" + ] + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "control-a-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "KQblducnQfd+g76kjTIadcFTVFZIgPuv734QYZzXSlQ=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "control-b-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "B00u+Tf4PT4DpamC8BNds2lpdxTz+IrGAfQybcxMSQU=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "node-us-central1-0-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "j3l6igdJLwzvbMgJHRg/Z3VpA9/OUO+MqRSOOHcSwJU=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "regional-front-us-central1-0-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "d917qR9zgyYuGWEdPxf8Q3DAk9NQKJEC0n+Wy2UvBOo=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "regional-bind-us-central1-0-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "pWd0nJIhjwXyT7awFL7kEb9HL4+cndYZGawrkiNiI0w=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "regional-bind-us-central1-1-password", + "namespace": "internal-dns-system" + }, + "stringData": { + "password": "Oc++tn+qOJ3lICo8IPqo4rpm0AUb1yIbwy2RFWt/fqg=" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "internal-dns-nats-config", + "namespace": "internal-dns-system" + }, + "stringData": { + "nats.conf": "{\"authorization\":{\"users\":[{\"password\":\"KQblducnQfd+g76kjTIadcFTVFZIgPuv734QYZzXSlQ=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.serving.\u003e\",\"dns.private.records.\u003e\",\"$KV.DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.\u003e\",\"$JS.ACK.DNS_PRIVATE.control-plane-us-central1-shared-0-acks.\u003e\",\"_INBOX.\u003e\"]},\"subscribe\":{\"allow\":[\"dns.private.acks.\u003e\",\"_INBOX.\u003e\"]}},\"user\":\"control-a\"},{\"password\":\"B00u+Tf4PT4DpamC8BNds2lpdxTz+IrGAfQybcxMSQU=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.serving.\u003e\",\"dns.private.records.\u003e\",\"$KV.DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.\u003e\",\"$JS.ACK.DNS_PRIVATE.control-plane-us-central1-shared-0-acks.\u003e\",\"_INBOX.\u003e\"]},\"subscribe\":{\"allow\":[\"dns.private.acks.\u003e\",\"_INBOX.\u003e\"]}},\"user\":\"control-b\"},{\"password\":\"j3l6igdJLwzvbMgJHRg/Z3VpA9/OUO+MqRSOOHcSwJU=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.acks.us-central1.shared-0.0195a8650ce9d5c927c91495\",\"$JS.API.STREAM.INFO.DNS_PRIVATE\",\"$JS.API.DIRECT.GET.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.STREAM.INFO.KV_DNS_PRIVATE_SNAPSHOTS\",\"$JS.API.CONSUMER.CREATE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.INFO.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.DELETE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"_INBOX.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.node-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.node-us-central1-0-serving\",\"$JS.ACK.DNS_PRIVATE.node-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.node-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.node-us-central1-0-records\",\"$JS.ACK.DNS_PRIVATE.node-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.node-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.node-us-central1-0-acks.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.node-us-central1-0-acks\",\"$JS.ACK.DNS_PRIVATE.node-us-central1-0-acks.\u003e\"]},\"subscribe\":{\"allow\":[\"_INBOX.\u003e\"]}},\"user\":\"node-us-central1-0\"},{\"password\":\"d917qR9zgyYuGWEdPxf8Q3DAk9NQKJEC0n+Wy2UvBOo=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.acks.us-central1.shared-0.92106c62061ed0349bc66280\",\"$JS.API.STREAM.INFO.DNS_PRIVATE\",\"$JS.API.DIRECT.GET.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.STREAM.INFO.KV_DNS_PRIVATE_SNAPSHOTS\",\"$JS.API.CONSUMER.CREATE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.INFO.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.DELETE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"_INBOX.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-front-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-front-us-central1-0-serving\",\"$JS.ACK.DNS_PRIVATE.regional-front-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-front-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-front-us-central1-0-records\",\"$JS.ACK.DNS_PRIVATE.regional-front-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-front-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-front-us-central1-0-acks.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-front-us-central1-0-acks\",\"$JS.ACK.DNS_PRIVATE.regional-front-us-central1-0-acks.\u003e\"]},\"subscribe\":{\"allow\":[\"_INBOX.\u003e\"]}},\"user\":\"regional-front-us-central1-0\"},{\"password\":\"pWd0nJIhjwXyT7awFL7kEb9HL4+cndYZGawrkiNiI0w=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.acks.us-central1.shared-0.b75afe155467f45fdab6d0a1\",\"$JS.API.STREAM.INFO.DNS_PRIVATE\",\"$JS.API.DIRECT.GET.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.STREAM.INFO.KV_DNS_PRIVATE_SNAPSHOTS\",\"$JS.API.CONSUMER.CREATE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.INFO.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.DELETE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"_INBOX.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-0-serving\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-0-serving.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-0-records\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-0-records.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-0-acks.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-0-acks\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-0-acks.\u003e\"]},\"subscribe\":{\"allow\":[\"_INBOX.\u003e\"]}},\"user\":\"regional-bind-us-central1-0\"},{\"password\":\"Oc++tn+qOJ3lICo8IPqo4rpm0AUb1yIbwy2RFWt/fqg=\",\"permissions\":{\"publish\":{\"allow\":[\"dns.private.acks.us-central1.shared-0.2600654ae9645c6e4439e8ac\",\"$JS.API.STREAM.INFO.DNS_PRIVATE\",\"$JS.API.DIRECT.GET.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.STREAM.INFO.KV_DNS_PRIVATE_SNAPSHOTS\",\"$JS.API.CONSUMER.CREATE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.INFO.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"$JS.API.CONSUMER.DELETE.KV_DNS_PRIVATE_SNAPSHOTS.\u003e\",\"_INBOX.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-1-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-serving\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-serving.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-1-serving\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-1-serving.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-1-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-records\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-records.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-1-records\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-1-records.\u003e\",\"$JS.API.CONSUMER.INFO.DNS_PRIVATE.regional-bind-us-central1-1-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-acks\",\"$JS.API.CONSUMER.CREATE.DNS_PRIVATE.regional-bind-us-central1-1-acks.\u003e\",\"$JS.API.CONSUMER.MSG.NEXT.DNS_PRIVATE.regional-bind-us-central1-1-acks\",\"$JS.ACK.DNS_PRIVATE.regional-bind-us-central1-1-acks.\u003e\"]},\"subscribe\":{\"allow\":[\"_INBOX.\u003e\"]}},\"user\":\"regional-bind-us-central1-1\"}]},\"cluster\":{\"name\":\"internal-dns-e2e\",\"port\":6222,\"routes\":[\"nats-route://internal-dns-nats-0.internal-dns-nats.internal-dns-system.svc.cluster.local:6222\",\"nats-route://internal-dns-nats-1.internal-dns-nats.internal-dns-system.svc.cluster.local:6222\",\"nats-route://internal-dns-nats-2.internal-dns-nats.internal-dns-system.svc.cluster.local:6222\"],\"tls\":{\"ca_file\":\"/tls/ca.crt\",\"cert_file\":\"/tls/tls.crt\",\"key_file\":\"/tls/tls.key\",\"verify\":true}},\"http_port\":8222,\"jetstream\":{\"store_dir\":\"/data\"},\"port\":4222,\"tls\":{\"ca_file\":\"/tls/ca.crt\",\"cert_file\":\"/tls/tls.crt\",\"key_file\":\"/tls/tls.key\",\"verify\":true}}\n" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "internal-dns-rndc", + "namespace": "internal-dns-system" + }, + "stringData": { + "rndc.key": "key \"internal-dns\" { algorithm hmac-sha256; secret \"JAJrowpw8ZbYLQsbOJwdRqM0oGAkRZfGvWS18iWMgOw=\"; };\n" + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "nats", + "namespace": "internal-dns-system" + }, + "spec": { + "ports": [ + { + "name": "client", + "port": 4222 + }, + { + "name": "cluster", + "port": 6222 + } + ], + "selector": { + "app": "internal-dns-nats" + } + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "internal-dns-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "name": "client", + "port": 4222 + }, + { + "name": "cluster", + "port": 6222 + } + ], + "publishNotReadyAddresses": true, + "selector": { + "app": "internal-dns-nats" + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "StatefulSet", + "metadata": { + "name": "internal-dns-nats", + "namespace": "internal-dns-system" + }, + "spec": { + "podManagementPolicy": "Parallel", + "replicas": 3, + "selector": { + "matchLabels": { + "app": "internal-dns-nats" + } + }, + "serviceName": "internal-dns-nats", + "template": { + "metadata": { + "labels": { + "app": "internal-dns-nats" + } + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "args": [ + "-c", + "/config/nats.conf", + "-n", + "$(POD_NAME)" + ], + "command": [ + "nats-server" + ], + "env": [ + { + "name": "POD_NAME", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.name" + } + } + } + ], + "image": "nats@sha256:e4bf19f15fd3218814a4e3c9e0064e1334bd8aa20d5984b9f1a0afd084f8cc00", + "name": "nats", + "readinessProbe": { + "httpGet": { + "path": "/healthz?js-enabled-only=true", + "port": 8222 + } + }, + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/tls", + "name": "tls", + "readOnly": true + }, + { + "mountPath": "/data", + "name": "data" + } + ] + } + ], + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "name": "config", + "secret": { + "secretName": "internal-dns-nats-config" + } + }, + { + "name": "tls", + "secret": { + "secretName": "internal-dns-nats-server" + } + } + ] + } + }, + "volumeClaimTemplates": [ + { + "metadata": { + "name": "data" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "1Gi" + } + } + } + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "internal-dns-member": "node-us-central1-0", + "internal-dns-role": "node" + }, + "name": "internal-dns-node", + "namespace": "internal-dns-system" + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=agent --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "agent", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=watchdog --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "watchdog", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/usr/local/sbin/internal-dns-daemons", + "node" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "serving", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "add": [ + "NET_BIND_SERVICE" + ], + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + } + ], + "initContainers": [ + { + "command": [ + "/bin/sh", + "-ec", + "ip -6 route replace local fd53::/64 dev lo" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "destination-fixture", + "securityContext": { + "capabilities": { + "add": [ + "NET_ADMIN" + ], + "drop": [ + "ALL" + ] + }, + "runAsUser": 0 + } + } + ], + "securityContext": { + "fsGroup": 65532, + "fsGroupChangePolicy": "OnRootMismatch", + "sysctls": [ + { + "name": "net.ipv4.ip_unprivileged_port_start", + "value": "0" + } + ] + }, + "serviceAccountName": "internal-dns-fleet", + "shareProcessNamespace": true, + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "internal-dns-node-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "node-us-central1-0-nats" + } + }, + { + "secret": { + "name": "node-us-central1-0-password" + } + } + ] + } + }, + { + "name": "state", + "persistentVolumeClaim": { + "claimName": "internal-dns-node-state" + } + }, + { + "name": "daemon-config", + "persistentVolumeClaim": { + "claimName": "internal-dns-node-config" + } + }, + { + "emptyDir": { + "medium": "Memory" + }, + "name": "lease" + }, + { + "emptyDir": {}, + "name": "runtime" + }, + { + "name": "rndc", + "secret": { + "secretName": "internal-dns-rndc" + } + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-node-state", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-node-config", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "internal-dns-member": "regional-front-us-central1-0", + "internal-dns-role": "regional-dnsdist" + }, + "name": "internal-dns-regional-front", + "namespace": "internal-dns-system" + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=agent --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "agent", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=watchdog --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "watchdog", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/usr/local/sbin/internal-dns-daemons", + "regional-dnsdist" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "serving", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "add": [ + "NET_BIND_SERVICE" + ], + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + } + ], + "initContainers": [ + { + "command": [ + "/bin/sh", + "-ec", + "ip -6 route replace local fd54::/64 dev lo" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "destination-fixture", + "securityContext": { + "capabilities": { + "add": [ + "NET_ADMIN" + ], + "drop": [ + "ALL" + ] + }, + "runAsUser": 0 + } + } + ], + "securityContext": { + "fsGroup": 65532, + "fsGroupChangePolicy": "OnRootMismatch", + "sysctls": [ + { + "name": "net.ipv4.ip_unprivileged_port_start", + "value": "0" + } + ] + }, + "serviceAccountName": "internal-dns-fleet", + "shareProcessNamespace": true, + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "internal-dns-regional-front-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "regional-front-us-central1-0-nats" + } + }, + { + "secret": { + "name": "regional-front-us-central1-0-password" + } + } + ] + } + }, + { + "name": "state", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-front-state" + } + }, + { + "name": "daemon-config", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-front-config" + } + }, + { + "emptyDir": { + "medium": "Memory" + }, + "name": "lease" + }, + { + "emptyDir": {}, + "name": "runtime" + }, + { + "name": "rndc", + "secret": { + "secretName": "internal-dns-rndc" + } + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-front-state", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-front-config", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "internal-dns-member": "regional-bind-us-central1-0", + "internal-dns-role": "regional-bind" + }, + "name": "internal-dns-regional-bind-0", + "namespace": "internal-dns-system" + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=agent --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "agent", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=watchdog --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "watchdog", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/usr/local/sbin/internal-dns-daemons", + "regional-bind" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "serving", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "add": [ + "NET_BIND_SERVICE" + ], + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + } + ], + "initContainers": [], + "securityContext": { + "fsGroup": 65532, + "fsGroupChangePolicy": "OnRootMismatch", + "sysctls": [ + { + "name": "net.ipv4.ip_unprivileged_port_start", + "value": "0" + } + ] + }, + "serviceAccountName": "internal-dns-fleet", + "shareProcessNamespace": true, + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "internal-dns-regional-bind-0-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "regional-bind-us-central1-0-nats" + } + }, + { + "secret": { + "name": "regional-bind-us-central1-0-password" + } + } + ] + } + }, + { + "name": "state", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-bind-0-state" + } + }, + { + "name": "daemon-config", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-bind-0-config" + } + }, + { + "emptyDir": { + "medium": "Memory" + }, + "name": "lease" + }, + { + "emptyDir": {}, + "name": "runtime" + }, + { + "name": "rndc", + "secret": { + "secretName": "internal-dns-rndc" + } + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-bind-0-state", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-bind-0-config", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "labels": { + "internal-dns-member": "regional-bind-us-central1-1", + "internal-dns-role": "regional-bind" + }, + "name": "internal-dns-regional-bind-1", + "namespace": "internal-dns-system" + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=agent --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "agent", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ]; do sleep 1; done; exec /internal-dns --role=watchdog --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "watchdog", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "50m", + "memory": "64Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + }, + { + "command": [ + "/usr/local/sbin/internal-dns-daemons", + "regional-bind" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "serving", + "resources": { + "limits": { + "memory": "512Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "add": [ + "NET_BIND_SERVICE" + ], + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/lib/internal-dns", + "name": "state", + "readOnly": false + }, + { + "mountPath": "/etc/internal-dns", + "name": "daemon-config", + "readOnly": false + }, + { + "mountPath": "/run/internal-dns", + "name": "lease", + "readOnly": false + }, + { + "mountPath": "/tmp", + "name": "runtime", + "readOnly": false + }, + { + "mountPath": "/etc/bind", + "name": "rndc", + "readOnly": true + } + ] + } + ], + "initContainers": [], + "securityContext": { + "fsGroup": 65532, + "fsGroupChangePolicy": "OnRootMismatch", + "sysctls": [ + { + "name": "net.ipv4.ip_unprivileged_port_start", + "value": "0" + } + ] + }, + "serviceAccountName": "internal-dns-fleet", + "shareProcessNamespace": true, + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "internal-dns-regional-bind-1-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "regional-bind-us-central1-1-nats" + } + }, + { + "secret": { + "name": "regional-bind-us-central1-1-password" + } + } + ] + } + }, + { + "name": "state", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-bind-1-state" + } + }, + { + "name": "daemon-config", + "persistentVolumeClaim": { + "claimName": "internal-dns-regional-bind-1-config" + } + }, + { + "emptyDir": { + "medium": "Memory" + }, + "name": "lease" + }, + { + "emptyDir": {}, + "name": "runtime" + }, + { + "name": "rndc", + "secret": { + "secretName": "internal-dns-rndc" + } + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-bind-1-state", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "PersistentVolumeClaim", + "metadata": { + "name": "internal-dns-regional-bind-1-config", + "namespace": "internal-dns-system" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "512Mi" + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "internal-dns-admission", + "namespace": "internal-dns-system" + }, + "spec": { + "ports": [ + { + "name": "https", + "nodePort": 30443, + "port": 443, + "targetPort": 9443 + } + ], + "selector": { + "internal-dns-role": "control-plane" + }, + "type": "NodePort" + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "control-a", + "namespace": "internal-dns-system" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "control-a" + } + }, + "template": { + "metadata": { + "labels": { + "app": "control-a", + "internal-dns-role": "control-plane" + } + }, + "spec": { + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ] || [ ! -s /sources/project-a.json ]; do sleep 1; done; exec /internal-dns --role=control-plane --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "control-plane", + "ports": [ + { + "containerPort": 9443, + "name": "admission" + } + ], + "readinessProbe": { + "tcpSocket": { + "port": 9443 + } + }, + "resources": { + "limits": { + "memory": "768Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/admission", + "name": "admission", + "readOnly": true + }, + { + "mountPath": "/sources", + "name": "sources", + "readOnly": true + } + ] + } + ], + "serviceAccountName": "internal-dns-control-plane", + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "control-a-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "control-a-nats" + } + }, + { + "secret": { + "name": "control-a-password" + } + } + ] + } + }, + { + "name": "admission", + "secret": { + "secretName": "internal-dns-admission-tls" + } + }, + { + "name": "sources", + "secret": { + "optional": true, + "secretName": "internal-dns-sources" + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "control-b", + "namespace": "internal-dns-system" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "control-b" + } + }, + "template": { + "metadata": { + "labels": { + "app": "control-b", + "internal-dns-role": "control-plane" + } + }, + "spec": { + "containers": [ + { + "command": [ + "/bin/sh", + "-ec", + "while [ ! -s /config/config.json ] || [ ! -s /sources/project-a.json ]; do sleep 1; done; exec /internal-dns --role=control-plane --config=/config/config.json" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "control-plane", + "ports": [ + { + "containerPort": 9443, + "name": "admission" + } + ], + "readinessProbe": { + "tcpSocket": { + "port": 9443 + } + }, + "resources": { + "limits": { + "memory": "768Mi" + }, + "requests": { + "cpu": "100m", + "memory": "128Mi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true, + "runAsGroup": 65532, + "runAsNonRoot": true, + "runAsUser": 65532 + }, + "volumeMounts": [ + { + "mountPath": "/config", + "name": "config", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/nats", + "name": "nats", + "readOnly": true + }, + { + "mountPath": "/var/run/internal-dns/admission", + "name": "admission", + "readOnly": true + }, + { + "mountPath": "/sources", + "name": "sources", + "readOnly": true + } + ] + } + ], + "serviceAccountName": "internal-dns-control-plane", + "terminationGracePeriodSeconds": 2, + "volumes": [ + { + "configMap": { + "name": "control-b-config", + "optional": true + }, + "name": "config" + }, + { + "name": "nats", + "projected": { + "sources": [ + { + "secret": { + "name": "control-b-nats" + } + }, + { + "secret": { + "name": "control-b-password" + } + } + ] + } + }, + { + "name": "admission", + "secret": { + "secretName": "internal-dns-admission-tls" + } + }, + { + "name": "sources", + "secret": { + "optional": true, + "secretName": "internal-dns-sources" + } + } + ] + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "internal-dns-probe", + "namespace": "internal-dns-system" + }, + "spec": { + "automountServiceAccountToken": false, + "containers": [ + { + "command": [ + "/bin/sh", + "-c", + "sleep infinity" + ], + "image": "internal-dns-kubernetes:e2e", + "imagePullPolicy": "Never", + "name": "probe", + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true + } + } + ], + "securityContext": { + "runAsGroup": 65532, + "runAsUser": 65532 + }, + "terminationGracePeriodSeconds": 2 + } + } + ], + "kind": "List" +} diff --git a/.internal-dns-e2e/kubernetes/platform-internal.kubeconfig b/.internal-dns-e2e/kubernetes/platform-internal.kubeconfig new file mode 100644 index 00000000..33b19f20 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/platform-internal.kubeconfig @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJSkxHMktNYnFReHN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUwTlRkYUZ3MHpOakV3TURreU1qVTVOVGRhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUREaVdFVXNKSEN4S1Z3aWY3YkJXeTk3TDlRejE3WDBQakZqRE1MYWNta2lMZThDOTJoc1JmdW84NUEKeG02aGhyYXg2VG83cXREbUpHRXpqdVpEYittbnlubDc5c2Q0VEZ0M3JpZlQ0ZFZqalVvN0RHOUlwczhRZHdaZQo1ZGFCdVNNc0RPcUY4ayt1WXEvRmJnZFM3bjREcGt2dzdGNWpJTlpINWM3clRjck9sM01JZkVYVG4ySDBvdzZKCjhGeW4zdDBpUFkvRTl6U2NkRDgwRDJtZmxkVVh2VU0zWmpodDV0NVFwUkpIYzQ1cVY2MkFjYkxjV2hMZEE5UTUKS3hqVEx2aHZMY3dyZ0pNOW5iWmgrOTlwb2k5QU56dlFmU2I3U1A3OXVtL2VKRFZYUWEwTzJkYyttcnl6amk1dAorRHFOdVhNU0dsR0ltYnNZVWpoaldHSUxHaXR0QWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRVzl1d2N4ckp4a3VXNHFZR1Y0Y1lNSFJ0N05UQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQkg1R2xheVh2VgpLWmYwaFdsYit0UC9wOThobWR2Z1ZVcEVsOVhzM21LMFUwc1QwdHhqbzVBbUZzYzBZTmhyL05JdGdwK0VGWG1yCnFXQis5YU4ydVMxSmdsYjZRNWlJZVpWbk1BcDk1ZkZSenZFR200eGNVbGFHVUFpelJMVlN3OTFGVmRyOFBPb0sKTFUvMnlndElnZUZheVBqY0NoS2V1dHg2WklHZWpGQWZlemxkTVpKV092YXNvV2o2Y2JVS2ZoZ0ZFUnp1VHE0Nwo3QTEwcmovZ2phWWRzWldzcnovQ0E0SlZqYzlkYmtXd2tOTVVmZlR3TFlaMDJsUVB4ejgvdHpIaGZ0SEp6ZXg2CnhoMmJPZVhkM3ZKc3pwcXE0TDdjV0dRRlY4VUtTQmVTYnY4R3ZWUUcrU0pQeEl0ZW1NcGt0Y0tBMklIZ2FQWU4KZHo0RWpEclp4UEdICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://dns-control-control-plane:6443 + name: kind-dns-control +contexts: +- context: + cluster: kind-dns-control + user: kind-dns-control + name: kind-dns-control +current-context: kind-dns-control +kind: Config +users: +- name: kind-dns-control + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJTUJEUmQwbUZxTmd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUwTlRkYUZ3MHlOekV3TVRJeU1qVTVOVGRhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETzFndmMKbEd2RlR5VW52cm5RZnBPWGh4ZUw0dFBNeUFHenJtdHFqR1BuL3l4b0psWEZORTlxUHdpdGRjRncyUi9sd1JJTQpFQmh6QlozTmx3dGM3UXBnaDY3WXpVdUVKZmNXZUVRQmlxY1hpNXpFZE5EM0tjVFd2dUtYYzU3MFRRU21kSUZ0CnhLVC82N3RQd21vcm94QWh3bVZqVlJIcEZQQzgvdjNhUlBINE1raEQ4ZEtmSHptN25oZWNLTHhpN3ZiVmZsbTMKSUhvUjhwRytBaXcxRDhPWjZwaHFZSGZTTGF0UmJLWkM3NjJCNXpYYzZLNFBEOTYyQm52c01sV0hMSi8zOWg1dgpLWUd6dGV5a2VsckNqdmJxUjJsOTVTeHJxRHRmbEY3Z2k3WVRFMlVEbERKMm9OTE1FS0t6bDFXNHBpVnZMMUhLCnJDUE5hMXMxalZibFZ2VzlBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkJiMjdCekdzbkdTNWJpcApnWlhoeGd3ZEczczFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUN0YXdHLy9xWTZ0ZXArQUNyb05uMmNlNHFFCitRaDlRbmlXbSt3Sk1BbGRZWFFOOVJDQkVab2djUmltcldzcnNXTGhUVE1HSm9JczN3dzlEMlp5Rm1MNVFpcmkKUHNaM2J1YUdBaTJORlNZTmJVc0Fpa0pzd2lndk1YanVOWGg4SnQ4cll3ZWZPeDUvVWE3RE12REhZakVnOHdCOQo1NTMvSWVmZVQrOThoc0tNNWtOSHhVWEttYWhLWUlENEJ5QkFrdnRBaWoyNHZ6b053K0VtTUJqRXpXTkdBQlRzCjFzc0VpQWdVaDNtaXNyLzhyaktDZVNTRjVmTkU1UmRFdkRYc3lIYXBhenQxL01UbVh4UzBvamlISnlRb3JBajIKZGRkaGV6TTV6cHMrV0RWUkU0V0Y2L1lQcDExT3VEQVlLbUdvMXNYMVZGWnFVVmw3Y0Q5UGdCSUd3SjRICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBenRZTDNKUnJ4VThsSjc2NTBINlRsNGNYaStMVHpNZ0JzNjVyYW94ajUvOHNhQ1pWCnhUUlBhajhJclhYQmNOa2Y1Y0VTREJBWWN3V2R6WmNMWE8wS1lJZXUyTTFMaENYM0ZuaEVBWXFuRjR1Y3hIVFEKOXluRTFyN2lsM09lOUUwRXBuU0JiY1NrLyt1N1Q4SnFLNk1RSWNKbFkxVVI2UlR3dlA3OTJrVHgrREpJUS9IUwpueDg1dTU0WG5DaThZdTcyMVg1WnR5QjZFZktSdmdJc05RL0RtZXFZYW1CMzBpMnJVV3ltUXUrdGdlYzEzT2l1CkR3L2V0Z1o3N0RKVmh5eWY5L1llYnltQnM3WHNwSHBhd283MjZrZHBmZVVzYTZnN1g1UmU0SXUyRXhObEE1UXkKZHFEU3pCQ2lzNWRWdUtZbGJ5OVJ5cXdqeld0Yk5ZMVc1VmIxdlFJREFRQUJBb0lCQUFQYTE3Q2I0bVVyU3dVZwpDa3VwVk5VbFI3RVFseXpPdkZLRytXRDFqcFg1TFBDeTFqU3RSSjdIdXh3K041ZmZIVGVsanZYNkJhenFpK0dCCjQzRXZFY2tEV2NsemVnMnZCbEFxNVJLMy94dUU4bEJ6bHpCSXBpV25CUm1HRkNDY1Z0dlpEQVl5MkZHTGx1U3YKRlhybjNSbjJRZFBGaVVIckcvNFM0Nis0M1RyRHlrSjBkUGp6QjlFYXpJcE5senR4Rk9XZ0xPRDd1YklsQnR0MQpreFVyY0l0SFlPTEczTmNHTVJqb25heDE1VHdjL0hTU2JnWERYam9kcU92SWdKSTFXdE5FTW5xMDkrcXdXdlJ3CmlFUllqdUFsRDgxa2RJTWhZQ0VsNzhrREl4K0NmZ3l3MHY1ekZmMy9nUVJoZFoyeDU5UjZaUGN5NG1vdlBjTi8KV3dIeDRsa0NnWUVBOWoweFVKeXVUdHp0c2hZM2ZRMHVOUUxrS3A2TlcvKzdxOEZpVVIzS08zVUg4ZHo3Q2xhMgpZeUUxd1h6Q1BoNmI4K1RlV09xbTJQeVN3RmtURWlBdUsyeVd0RDBXL1lFLzdMckgvbXc2dnIzb0wraWt0Q0R6CkxNWDdRWDBCejNNcTg4eXlSSDJFT0RGMndnQVl3eW5HdG83TW4yV1dWOTgrUWRxMW9ycTI5U01DZ1lFQTF3ai8KUS9teWtYNzhsUlF5Y0FzRFZnRTB3WkVOYjVsbURrNVZqQkduSkNzaElreEwwWE5ucjlyU1NiRGp1Um9oWm1rWAoreWhQZzZXWU9VUWpNNEVQQVgrcEx5UWRsWFNUSGh0aktyL28xU2k3ZExaUUxlSmF5ZXlWQnRxRE9ibjZVZU1hClNTWEVpd3Z5RFdYbjZFN2w4SzZ5Q2VqTXBBRDkzTGZZamxqb1I1OENnWUJEQjRWVkhGR1d4aTB1UFQzOW5Jam4KaFZIM0NCSVM2UnA5TnJjOHZZOC9nQ3daZXI0Ym9sOGp2UGFWSlJEN04vbmIrb0ZNVXhQNEhVa1ZjeW50MVRTOApQV01JelZoa3licE5Cc3RLUnN2M0FGc2tMNEFtU0pIcHJqc25LSkJGbUdRbzJaL3RaUDJKRG0zMWdYRThMek1ZCm51YTB2VUtmeVBWNWw3KzUvSG14WVFLQmdCK3FpSVRIR1ovSmRvS3hwYmZRTjE2TDhPdnNZM0Vpd3QrcWMzaWMKQk9RVG9kNStJOUFsVVlpWkRjeWlTdmF3UFdqcVl3aXVaLzgzL0pHNlVnQzhxYXdMMDZ0SWd6N0dHd0FkQWw2QwpRbFJabWpsV3U3bjJFbTZYODE0K0FZTVh1eUFoOUhYL0pORmhJZlVOSTdpWEhjOXl1bTh4SkNtbXA3dkFLeGpUClNPbDdBb0dBWVdqZ1ZNdDBVbVlpaFBzT0VIV0tubkFrT3pQREJYV0pzZS84NDBnK2g5RUd0R1FiQnR4NDdsb3cKZnpQY3FFK0k5WXJxamxjc1pzZUF4NTRMSldTdXVlK0lHRXZPWldvWExiSGUxVDZrUzc3QnYxa1NaUHJoWmo4agoxWmZQR3dNNHJFUDQ0b3ovVThnckdhejhwZ2U0d0c0dUdvMWNnV2M1bE1RR1MrSktiNDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + diff --git a/.internal-dns-e2e/kubernetes/platform.kubeconfig b/.internal-dns-e2e/kubernetes/platform.kubeconfig new file mode 100644 index 00000000..2889e57a --- /dev/null +++ b/.internal-dns-e2e/kubernetes/platform.kubeconfig @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJSkxHMktNYnFReHN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUwTlRkYUZ3MHpOakV3TURreU1qVTVOVGRhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUREaVdFVXNKSEN4S1Z3aWY3YkJXeTk3TDlRejE3WDBQakZqRE1MYWNta2lMZThDOTJoc1JmdW84NUEKeG02aGhyYXg2VG83cXREbUpHRXpqdVpEYittbnlubDc5c2Q0VEZ0M3JpZlQ0ZFZqalVvN0RHOUlwczhRZHdaZQo1ZGFCdVNNc0RPcUY4ayt1WXEvRmJnZFM3bjREcGt2dzdGNWpJTlpINWM3clRjck9sM01JZkVYVG4ySDBvdzZKCjhGeW4zdDBpUFkvRTl6U2NkRDgwRDJtZmxkVVh2VU0zWmpodDV0NVFwUkpIYzQ1cVY2MkFjYkxjV2hMZEE5UTUKS3hqVEx2aHZMY3dyZ0pNOW5iWmgrOTlwb2k5QU56dlFmU2I3U1A3OXVtL2VKRFZYUWEwTzJkYyttcnl6amk1dAorRHFOdVhNU0dsR0ltYnNZVWpoaldHSUxHaXR0QWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRVzl1d2N4ckp4a3VXNHFZR1Y0Y1lNSFJ0N05UQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQkg1R2xheVh2VgpLWmYwaFdsYit0UC9wOThobWR2Z1ZVcEVsOVhzM21LMFUwc1QwdHhqbzVBbUZzYzBZTmhyL05JdGdwK0VGWG1yCnFXQis5YU4ydVMxSmdsYjZRNWlJZVpWbk1BcDk1ZkZSenZFR200eGNVbGFHVUFpelJMVlN3OTFGVmRyOFBPb0sKTFUvMnlndElnZUZheVBqY0NoS2V1dHg2WklHZWpGQWZlemxkTVpKV092YXNvV2o2Y2JVS2ZoZ0ZFUnp1VHE0Nwo3QTEwcmovZ2phWWRzWldzcnovQ0E0SlZqYzlkYmtXd2tOTVVmZlR3TFlaMDJsUVB4ejgvdHpIaGZ0SEp6ZXg2CnhoMmJPZVhkM3ZKc3pwcXE0TDdjV0dRRlY4VUtTQmVTYnY4R3ZWUUcrU0pQeEl0ZW1NcGt0Y0tBMklIZ2FQWU4KZHo0RWpEclp4UEdICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://127.0.0.1:59799 + name: kind-dns-control +contexts: +- context: + cluster: kind-dns-control + user: kind-dns-control + name: kind-dns-control +current-context: kind-dns-control +kind: Config +users: +- name: kind-dns-control + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJTUJEUmQwbUZxTmd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUwTlRkYUZ3MHlOekV3TVRJeU1qVTVOVGRhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETzFndmMKbEd2RlR5VW52cm5RZnBPWGh4ZUw0dFBNeUFHenJtdHFqR1BuL3l4b0psWEZORTlxUHdpdGRjRncyUi9sd1JJTQpFQmh6QlozTmx3dGM3UXBnaDY3WXpVdUVKZmNXZUVRQmlxY1hpNXpFZE5EM0tjVFd2dUtYYzU3MFRRU21kSUZ0CnhLVC82N3RQd21vcm94QWh3bVZqVlJIcEZQQzgvdjNhUlBINE1raEQ4ZEtmSHptN25oZWNLTHhpN3ZiVmZsbTMKSUhvUjhwRytBaXcxRDhPWjZwaHFZSGZTTGF0UmJLWkM3NjJCNXpYYzZLNFBEOTYyQm52c01sV0hMSi8zOWg1dgpLWUd6dGV5a2VsckNqdmJxUjJsOTVTeHJxRHRmbEY3Z2k3WVRFMlVEbERKMm9OTE1FS0t6bDFXNHBpVnZMMUhLCnJDUE5hMXMxalZibFZ2VzlBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkJiMjdCekdzbkdTNWJpcApnWlhoeGd3ZEczczFNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUN0YXdHLy9xWTZ0ZXArQUNyb05uMmNlNHFFCitRaDlRbmlXbSt3Sk1BbGRZWFFOOVJDQkVab2djUmltcldzcnNXTGhUVE1HSm9JczN3dzlEMlp5Rm1MNVFpcmkKUHNaM2J1YUdBaTJORlNZTmJVc0Fpa0pzd2lndk1YanVOWGg4SnQ4cll3ZWZPeDUvVWE3RE12REhZakVnOHdCOQo1NTMvSWVmZVQrOThoc0tNNWtOSHhVWEttYWhLWUlENEJ5QkFrdnRBaWoyNHZ6b053K0VtTUJqRXpXTkdBQlRzCjFzc0VpQWdVaDNtaXNyLzhyaktDZVNTRjVmTkU1UmRFdkRYc3lIYXBhenQxL01UbVh4UzBvamlISnlRb3JBajIKZGRkaGV6TTV6cHMrV0RWUkU0V0Y2L1lQcDExT3VEQVlLbUdvMXNYMVZGWnFVVmw3Y0Q5UGdCSUd3SjRICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBenRZTDNKUnJ4VThsSjc2NTBINlRsNGNYaStMVHpNZ0JzNjVyYW94ajUvOHNhQ1pWCnhUUlBhajhJclhYQmNOa2Y1Y0VTREJBWWN3V2R6WmNMWE8wS1lJZXUyTTFMaENYM0ZuaEVBWXFuRjR1Y3hIVFEKOXluRTFyN2lsM09lOUUwRXBuU0JiY1NrLyt1N1Q4SnFLNk1RSWNKbFkxVVI2UlR3dlA3OTJrVHgrREpJUS9IUwpueDg1dTU0WG5DaThZdTcyMVg1WnR5QjZFZktSdmdJc05RL0RtZXFZYW1CMzBpMnJVV3ltUXUrdGdlYzEzT2l1CkR3L2V0Z1o3N0RKVmh5eWY5L1llYnltQnM3WHNwSHBhd283MjZrZHBmZVVzYTZnN1g1UmU0SXUyRXhObEE1UXkKZHFEU3pCQ2lzNWRWdUtZbGJ5OVJ5cXdqeld0Yk5ZMVc1VmIxdlFJREFRQUJBb0lCQUFQYTE3Q2I0bVVyU3dVZwpDa3VwVk5VbFI3RVFseXpPdkZLRytXRDFqcFg1TFBDeTFqU3RSSjdIdXh3K041ZmZIVGVsanZYNkJhenFpK0dCCjQzRXZFY2tEV2NsemVnMnZCbEFxNVJLMy94dUU4bEJ6bHpCSXBpV25CUm1HRkNDY1Z0dlpEQVl5MkZHTGx1U3YKRlhybjNSbjJRZFBGaVVIckcvNFM0Nis0M1RyRHlrSjBkUGp6QjlFYXpJcE5senR4Rk9XZ0xPRDd1YklsQnR0MQpreFVyY0l0SFlPTEczTmNHTVJqb25heDE1VHdjL0hTU2JnWERYam9kcU92SWdKSTFXdE5FTW5xMDkrcXdXdlJ3CmlFUllqdUFsRDgxa2RJTWhZQ0VsNzhrREl4K0NmZ3l3MHY1ekZmMy9nUVJoZFoyeDU5UjZaUGN5NG1vdlBjTi8KV3dIeDRsa0NnWUVBOWoweFVKeXVUdHp0c2hZM2ZRMHVOUUxrS3A2TlcvKzdxOEZpVVIzS08zVUg4ZHo3Q2xhMgpZeUUxd1h6Q1BoNmI4K1RlV09xbTJQeVN3RmtURWlBdUsyeVd0RDBXL1lFLzdMckgvbXc2dnIzb0wraWt0Q0R6CkxNWDdRWDBCejNNcTg4eXlSSDJFT0RGMndnQVl3eW5HdG83TW4yV1dWOTgrUWRxMW9ycTI5U01DZ1lFQTF3ai8KUS9teWtYNzhsUlF5Y0FzRFZnRTB3WkVOYjVsbURrNVZqQkduSkNzaElreEwwWE5ucjlyU1NiRGp1Um9oWm1rWAoreWhQZzZXWU9VUWpNNEVQQVgrcEx5UWRsWFNUSGh0aktyL28xU2k3ZExaUUxlSmF5ZXlWQnRxRE9ibjZVZU1hClNTWEVpd3Z5RFdYbjZFN2w4SzZ5Q2VqTXBBRDkzTGZZamxqb1I1OENnWUJEQjRWVkhGR1d4aTB1UFQzOW5Jam4KaFZIM0NCSVM2UnA5TnJjOHZZOC9nQ3daZXI0Ym9sOGp2UGFWSlJEN04vbmIrb0ZNVXhQNEhVa1ZjeW50MVRTOApQV01JelZoa3licE5Cc3RLUnN2M0FGc2tMNEFtU0pIcHJqc25LSkJGbUdRbzJaL3RaUDJKRG0zMWdYRThMek1ZCm51YTB2VUtmeVBWNWw3KzUvSG14WVFLQmdCK3FpSVRIR1ovSmRvS3hwYmZRTjE2TDhPdnNZM0Vpd3QrcWMzaWMKQk9RVG9kNStJOUFsVVlpWkRjeWlTdmF3UFdqcVl3aXVaLzgzL0pHNlVnQzhxYXdMMDZ0SWd6N0dHd0FkQWw2QwpRbFJabWpsV3U3bjJFbTZYODE0K0FZTVh1eUFoOUhYL0pORmhJZlVOSTdpWEhjOXl1bTh4SkNtbXA3dkFLeGpUClNPbDdBb0dBWVdqZ1ZNdDBVbVlpaFBzT0VIV0tubkFrT3pQREJYV0pzZS84NDBnK2g5RUd0R1FiQnR4NDdsb3cKZnpQY3FFK0k5WXJxamxjc1pzZUF4NTRMSldTdXVlK0lHRXZPWldvWExiSGUxVDZrUzc3QnYxa1NaUHJoWmo4agoxWmZQR3dNNHJFUDQ0b3ovVThnckdhejhwZ2U0d0c0dUdvMWNnV2M1bE1RR1MrSktiNDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/.internal-dns-e2e/kubernetes/project-a-bootstrap.json b/.internal-dns-e2e/kubernetes/project-a-bootstrap.json new file mode 100644 index 00000000..228cb7ea --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-a-bootstrap.json @@ -0,0 +1,946 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "project-e2e" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "compute-system" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "network-system" + } + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "automountServiceAccountToken": false, + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-fleet", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-vpc-integration", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status", + "dnsmanagednamespaces", + "dnsmanagednamespaces/status" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-product-writer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations", + "dnsrecordcontributions" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants", + "dnsresolvercontexts", + "dnsmanagednamespaces", + "dnsnamingpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsrecordcontributions/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-customer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsnamingpolicies", + "dnsregistrations" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-grant-issuer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "internal-dns-control-plane" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsregistrations", + "dnscontributiongrants", + "dnsrecordcontributions", + "dnsnamingpolicies", + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnszoneassociations" + ], + "verbs": [ + "create", + "patch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings", + "dnspublicationmanifests", + "dnspublicationchunks", + "dnstransportoutboxes", + "dnspublicationownerships", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings/status", + "dnspublicationmanifests/status", + "dnstransportoutboxes/status", + "dnspublicationownerships/status", + "dnsmanagednamespaces/status", + "dnszones/status", + "dnszoneassociations/status", + "dnsnamingpolicies/status", + "dnsregistrations/status", + "dnscontributiongrants/status", + "dnsrecordcontributions/status", + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "" + ], + "resources": [ + "configmaps" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "internal-dns-source-worker", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "internal-dns-control-plane" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-customer", + "namespace": "project-e2e" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-customer", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-customer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-customer-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-a" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-customer-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-customer-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-customer-self", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-customer" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-customer-self", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-customer-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-publisher", + "namespace": "compute-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-publisher", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-product-writer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-publisher-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-a" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-publisher-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-publisher-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-publisher-self", + "namespace": "compute-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-publisher" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-publisher-self", + "namespace": "compute-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-publisher-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-grant-issuer", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-grant-issuer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-grant-issuer-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-a" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-grant-issuer-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-grant-issuer-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-grant-issuer-self", + "namespace": "compute-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-grant-issuer" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-grant-issuer-self", + "namespace": "compute-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-grant-issuer-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-vpc-integration", + "namespace": "network-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-vpc-integration", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-vpc-integration" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-vpc-integration-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-a" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-vpc-integration-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-vpc-integration-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-vpc-integration-self", + "namespace": "network-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-vpc-integration" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-vpc-integration-self", + "namespace": "network-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-vpc-integration-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "internal-dns-control-plane-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-a" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "internal-dns-control-plane-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "internal-dns-control-plane-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-control-plane-self", + "namespace": "internal-dns-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "internal-dns-control-plane" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "internal-dns-control-plane-self", + "namespace": "internal-dns-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-control-plane-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "dns.networking.miloapis.com/v1alpha1", + "kind": "DNSZoneClass", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "private-bind" + }, + "spec": { + "controllerName": "bind", + "nameServerPolicy": { + "mode": "Static", + "static": { + "servers": [ + "ns.internal." + ] + } + } + } + } + ], + "kind": "List" +} diff --git a/.internal-dns-e2e/kubernetes/project-a-internal.kubeconfig b/.internal-dns-e2e/kubernetes/project-a-internal.kubeconfig new file mode 100644 index 00000000..07ced9a1 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-a-internal.kubeconfig @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJVVArK3dzVkpTWDR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUzTURaYUZ3MHpOakV3TURreU16QXlNRFphTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUN1ck9uQy9yd2ZCaWU3Ky9pbmZOMUFNRHZaaXQvMWxKTzhvNFpRRmp0K3E4VHJLcEJhUERVWEFUeHgKNHpzREk3QVYydFRXMWJTZEVLbmZoN0ViM2hKSGpnZXFvckUwS0hJUENMM0N1TlhlZ3FqWVMzOHN0a2dMbWpSWgpidkRCMWJXejVYd3licUFMSVBZM2txckJ1aW1jVjJuclREdW83YUEybmVCZ3Fzc1QxSjhzblRMN1dodDd6TWlGCnM4WjQ5NWI1ODlYcFNGWlFtbXdEdnNGQm5oUTJIQUJQK0E0R2svdWdUWkhQMm1uZlhXQjl1K3VCUUJPakEyU24KNUh2Z1hZUmF5cjBPTU04Q0dXMzU0Q3ZHenhKbkdjN2Y4ME53a25veXU5WVVSVzR0VEZQOWhrUGVNMUo4T2lsbQp0SGZ0RFNQWkJWNlRLdjlWV3l3bmM5aTcyMWNqQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRSWlMbDhhdWRmUFNiYkxmQ1JUNzZiRlkxazlEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQlRaT1VubVF5dApmQnp3ZTJCSHJ6ckNKWGVVbWlRRUtBc3AweDFka3NZcXZCa2ZJVTJXZjVxektmb3ZhcXdIaG44YkQwdEtqc2ZHCmVQN2J1SmcwVVM2NFZJRmRuZEk2ajl3QjFXN0t6QzRQbWJnSUY3bER0eWJrNTFEMXlXTzZ5ZUIzUUdyUjdoQ2EKY3JMRjZyQjJpWlNBMnkyS1JpMFJUR05Ed0N3aGtXRUhFdHpoOEVyRkRoS2NjQmFua25LaGk3N3dDMDNrRjYvdgp2UkkyM1p5THhCMVFja0hneXVYc1ZHVlZrbE5BejU4VUp6cjR2RkI1M25JL2NYRHdMd1A0Q1p1UCttTFB1aXFmCmh5MmU5Z3hiZnF1ZFdiZzA4cm91Y2lzL0hQby83bG9JckZVbkV2eUVoTDcrclN5VWxLSWF4ODM4N3lmWk5qQzgKRUlxMjAvUFkvV1ZDCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://dns-upstream-control-plane:6443 + name: kind-dns-upstream +contexts: +- context: + cluster: kind-dns-upstream + user: kind-dns-upstream + name: kind-dns-upstream +current-context: kind-dns-upstream +kind: Config +users: +- name: kind-dns-upstream + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJUUwvbmZtdFdzUjR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUzTURaYUZ3MHlOekV3TVRJeU16QXlNRFphTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdk5uaGYKMk5xMFJGUys5MFVCOXBFL0tmZkw5TEZuYWRpRUhVaHpqNkRxZ0lHWDlmSC9oMklUT21NWTJJMU9VR1Z5eGVhbQpTWUZVNjFTWGdyVy95enl1MUNIMUd5VXdBMzVyR1IwK1RMZUtoZkQwQ1pwY0FVZ3R2N2JDaDFuZ3EyZEhGNEUwCjlwN245RWhpVno2ME1DV2Qzc0M1emhFb1BIVU0wQ1l2Sy9SazVOVmsrd1B5d3M2MVBaNjQ5NlVqYXh6cjN5dXEKbEVEM1F0WFhTdURxb3lJcVhZdlNBOUtTdjNrUEtOZnNrZVo1UVBiRGhPQmdGYVA0S3JHQ1dWNHJCa2lBSVdzWQo2TktzaWcrRHBUbTd5T2IwRUFDeWFMWWhOdjZEZXA5Nm5iNFhxa1dwRlBjTlh6Tno5KytpdG5QV0JJcHhxcCtCCitLUkdHWnF2cUtIVDYvY05BZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkFpSXVYeHE1MTg5SnRzdAo4SkZQdnBzVmpXVDBNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFRTHRybHp3RHdYU2hmOVNmYU1TQjgwWkhPCnEzdTY3Sm94V3IxN0grVFJ1RWJIVGJMeHI3bmk1aFVxOGRxL21iNEI3cEgxc2p1UllBbXFmZ3BLZFl6UE1EU0sKbk1nNUlrWjRLNnloampvRG5rTWgxVVNIb25XQVd2bXJoS2p3OGIvaEpkVUJsYjVYcUpZU0VDVWJWMHFobVk0Qgp5d2xlU0RuM21iNlhvWUsySnU1eTJaOWxVUHVtNVV6NlpFS2xxSU1vdVQvRDRvN2xTTDh6VlVTSkFKVEMvUmdaCnprSjlrNFlPZFRSRzNQRXJmNEpXRnpzTXY2UWd6c3AyTkhESzFiL0J4MVhYcmFySS8rWGh2cUJqWlFRbVFPUSsKTE5QSUFDNkdEckRUNkRYSVFLbDg2VXZWMDQxZVRvM1NLZ2JGWi9nWUdsY2hKTTA0T01MZ3hNcHpqSFp2Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBcnpaNFg5amF0RVJVdnZkRkFmYVJQeW4zeS9TeFoyblloQjFJYzQrZzZvQ0JsL1h4Ci80ZGlFenBqR05pTlRsQmxjc1htcGttQlZPdFVsNEsxdjhzOHJ0UWg5UnNsTUFOK2F4a2RQa3kzaW9YdzlBbWEKWEFGSUxiKzJ3b2RaNEt0blJ4ZUJOUGFlNS9SSVlsYyt0REFsbmQ3QXVjNFJLRHgxRE5BbUx5djBaT1RWWlBzRAo4c0xPdFQyZXVQZWxJMnNjNjk4cnFwUkE5MExWMTByZzZxTWlLbDJMMGdQU2tyOTVEeWpYN0pIbWVVRDJ3NFRnCllCV2orQ3F4Z2xsZUt3WklnQ0ZyR09qU3JJb1BnNlU1dThqbTlCQUFzbWkySVRiK2czcWZlcDIrRjZwRnFSVDMKRFY4emMvZnZvclp6MWdTS2NhcWZnZmlrUmhtYXI2aWgwK3YzRFFJREFRQUJBb0lCQUNyQWVMaXlXaEw0UlZGZgpWa2czV0pOMzh3MmlCQk4rb1NVRFhDVk9XMjQ4c3VKWVVaY08rcUJFYzkraEgwR0hubWxWM29TZWxOOGFtTVpyClQxdWdCSXVJR1UwTU5aTzBsb2tkVktSWjVjTDRFYVlLSGM3clJTaGNpZ1NITUNnQXlxOXZEY2pWa0VwNnNackYKUFBpTWN2RkZZL015VTc5a21RN3FUeGJkZThHT0tTU3VDM1R3UXo0VnQrdVRIRi9sb2g5QlRuZTNyWmUzMVQ5bQo3MkNDd29WZG9QQTNYa2dMcENwWCtGSjA5TjBhZm44UW40SnZiMXAvWjAyL3ZqempjMys0N1UxcnZaVFVZWDVPCm80MmZMT1FmUzBPejB1TGtQVFdCVVZkUmhhQ3lvNDBSeFozTi9QNDI0eHVPNFpoQWV0L3psMklUSjA1WVF1QVcKdlJqYytpVUNnWUVBMUdZL2ZUc0Fudm9qZXRaNjFWMWV5dW1qNkt3T3FBeVRXUWpReGZIRk1DOEkvdlZLTWtuagpsN2laVnptZFFOYzMrSjl2L01xaDIyemIzUGM1NzBmNmVBT3dubzNVeDFTQk8rUlgyTEZuMzAyMXZmdEFpZi9SCkNDZVI4cWhZRDdVTEhaeE9oekpCeS9tTFlWcUF3aWc4aDBWVnA3eG8yZkViOUJVcjdxanhObHNDZ1lFQTB5NEoKUDI0UzFrQS81OFFtWlZSaVVnYkpoYXZkY3lVbzJqTUd6K0g4NS9sZjhadis2ODczczFRWHp3cFA5UTE3V2ZuRQpsQ0ZTNTMyYnJUa1BWQXV5cjZJQnhySEZlTm8wVFV1emZKcVhyU21DNnRONXRud2VpMmd0NnA4NHZ5elgyem8wCkdvamlCN0Z1MHV2ckFuVkl5V3Y1NjFhTDhTMkpHZ251bGIvdEZMY0NnWUVBblU2bEV3RFI4dXhidnF4L0NPdEEKTzVSUyt3NERSTjhiZWtPWExDTmw0Y3ZVSTlCVGhXOUlDcVNEblNCa29STkV0dlNPb25FSTgxUnFlZnI0ZzRtUgpyYlU5NkJBdldQbnpSUUpYTlh3ZTdEbkhzczhVKzUvbGx0NkhyMnFTM2Y4K0hrNFhzdlc3MlhBV2xLSE0zditoCjVLV3JUTEcwRGtZT1ovWU9OWklXSkNVQ2dZRUFoOFpaZThheHBiNjVWN0h0WGNqYlRoRy9ENHBvWHRmTlpEemEKYkYzSDFQMm9paGJaMTVMQi96SlovR3VNN09rMkhhdFFrRXk4bU1sUmhoWGhuUS9JV3RPMmdLRmJiVkFkd0hTagplY0dMTTNJMUVmWFZrMVpKN1NlV0hmc3c2UGxUNzBlRXFHckpxRmlVQUZoZVNMNVBoZkUrUmVqbklMRStMTmVsCjVmSDM1QkVDZ1lFQWd1dEJtUGxrWWYvMkovYlFaNmg4WnlLd25GYytUQy9IZGlVbzhFNjlCdXNiOGxQZXlYencKTHdlQXh2WThkNkNWeC9RTGZPV3h2NVJrQ29vR2xNV2orbmJOWFY3VnZ3V1lGVkdSczM2cldRMnpqZCsxZ1BkOApTQjlUZVNFK0lvU0QwNjRHU01uNml3cGZUWm11NEhCUDZhbTJYSkdaNWpNTXZReVVvMzhDNTFrPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + diff --git a/.internal-dns-e2e/kubernetes/project-a.kubeconfig b/.internal-dns-e2e/kubernetes/project-a.kubeconfig new file mode 100644 index 00000000..6a1bd2c6 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-a.kubeconfig @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJVVArK3dzVkpTWDR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUzTURaYUZ3MHpOakV3TURreU16QXlNRFphTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUN1ck9uQy9yd2ZCaWU3Ky9pbmZOMUFNRHZaaXQvMWxKTzhvNFpRRmp0K3E4VHJLcEJhUERVWEFUeHgKNHpzREk3QVYydFRXMWJTZEVLbmZoN0ViM2hKSGpnZXFvckUwS0hJUENMM0N1TlhlZ3FqWVMzOHN0a2dMbWpSWgpidkRCMWJXejVYd3licUFMSVBZM2txckJ1aW1jVjJuclREdW83YUEybmVCZ3Fzc1QxSjhzblRMN1dodDd6TWlGCnM4WjQ5NWI1ODlYcFNGWlFtbXdEdnNGQm5oUTJIQUJQK0E0R2svdWdUWkhQMm1uZlhXQjl1K3VCUUJPakEyU24KNUh2Z1hZUmF5cjBPTU04Q0dXMzU0Q3ZHenhKbkdjN2Y4ME53a25veXU5WVVSVzR0VEZQOWhrUGVNMUo4T2lsbQp0SGZ0RFNQWkJWNlRLdjlWV3l3bmM5aTcyMWNqQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRSWlMbDhhdWRmUFNiYkxmQ1JUNzZiRlkxazlEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQlRaT1VubVF5dApmQnp3ZTJCSHJ6ckNKWGVVbWlRRUtBc3AweDFka3NZcXZCa2ZJVTJXZjVxektmb3ZhcXdIaG44YkQwdEtqc2ZHCmVQN2J1SmcwVVM2NFZJRmRuZEk2ajl3QjFXN0t6QzRQbWJnSUY3bER0eWJrNTFEMXlXTzZ5ZUIzUUdyUjdoQ2EKY3JMRjZyQjJpWlNBMnkyS1JpMFJUR05Ed0N3aGtXRUhFdHpoOEVyRkRoS2NjQmFua25LaGk3N3dDMDNrRjYvdgp2UkkyM1p5THhCMVFja0hneXVYc1ZHVlZrbE5BejU4VUp6cjR2RkI1M25JL2NYRHdMd1A0Q1p1UCttTFB1aXFmCmh5MmU5Z3hiZnF1ZFdiZzA4cm91Y2lzL0hQby83bG9JckZVbkV2eUVoTDcrclN5VWxLSWF4ODM4N3lmWk5qQzgKRUlxMjAvUFkvV1ZDCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://127.0.0.1:61329 + name: kind-dns-upstream +contexts: +- context: + cluster: kind-dns-upstream + user: kind-dns-upstream + name: kind-dns-upstream +current-context: kind-dns-upstream +kind: Config +users: +- name: kind-dns-upstream + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJUUwvbmZtdFdzUjR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalUzTURaYUZ3MHlOekV3TVRJeU16QXlNRFphTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDdk5uaGYKMk5xMFJGUys5MFVCOXBFL0tmZkw5TEZuYWRpRUhVaHpqNkRxZ0lHWDlmSC9oMklUT21NWTJJMU9VR1Z5eGVhbQpTWUZVNjFTWGdyVy95enl1MUNIMUd5VXdBMzVyR1IwK1RMZUtoZkQwQ1pwY0FVZ3R2N2JDaDFuZ3EyZEhGNEUwCjlwN245RWhpVno2ME1DV2Qzc0M1emhFb1BIVU0wQ1l2Sy9SazVOVmsrd1B5d3M2MVBaNjQ5NlVqYXh6cjN5dXEKbEVEM1F0WFhTdURxb3lJcVhZdlNBOUtTdjNrUEtOZnNrZVo1UVBiRGhPQmdGYVA0S3JHQ1dWNHJCa2lBSVdzWQo2TktzaWcrRHBUbTd5T2IwRUFDeWFMWWhOdjZEZXA5Nm5iNFhxa1dwRlBjTlh6Tno5KytpdG5QV0JJcHhxcCtCCitLUkdHWnF2cUtIVDYvY05BZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkFpSXVYeHE1MTg5SnRzdAo4SkZQdnBzVmpXVDBNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFRTHRybHp3RHdYU2hmOVNmYU1TQjgwWkhPCnEzdTY3Sm94V3IxN0grVFJ1RWJIVGJMeHI3bmk1aFVxOGRxL21iNEI3cEgxc2p1UllBbXFmZ3BLZFl6UE1EU0sKbk1nNUlrWjRLNnloampvRG5rTWgxVVNIb25XQVd2bXJoS2p3OGIvaEpkVUJsYjVYcUpZU0VDVWJWMHFobVk0Qgp5d2xlU0RuM21iNlhvWUsySnU1eTJaOWxVUHVtNVV6NlpFS2xxSU1vdVQvRDRvN2xTTDh6VlVTSkFKVEMvUmdaCnprSjlrNFlPZFRSRzNQRXJmNEpXRnpzTXY2UWd6c3AyTkhESzFiL0J4MVhYcmFySS8rWGh2cUJqWlFRbVFPUSsKTE5QSUFDNkdEckRUNkRYSVFLbDg2VXZWMDQxZVRvM1NLZ2JGWi9nWUdsY2hKTTA0T01MZ3hNcHpqSFp2Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBcnpaNFg5amF0RVJVdnZkRkFmYVJQeW4zeS9TeFoyblloQjFJYzQrZzZvQ0JsL1h4Ci80ZGlFenBqR05pTlRsQmxjc1htcGttQlZPdFVsNEsxdjhzOHJ0UWg5UnNsTUFOK2F4a2RQa3kzaW9YdzlBbWEKWEFGSUxiKzJ3b2RaNEt0blJ4ZUJOUGFlNS9SSVlsYyt0REFsbmQ3QXVjNFJLRHgxRE5BbUx5djBaT1RWWlBzRAo4c0xPdFQyZXVQZWxJMnNjNjk4cnFwUkE5MExWMTByZzZxTWlLbDJMMGdQU2tyOTVEeWpYN0pIbWVVRDJ3NFRnCllCV2orQ3F4Z2xsZUt3WklnQ0ZyR09qU3JJb1BnNlU1dThqbTlCQUFzbWkySVRiK2czcWZlcDIrRjZwRnFSVDMKRFY4emMvZnZvclp6MWdTS2NhcWZnZmlrUmhtYXI2aWgwK3YzRFFJREFRQUJBb0lCQUNyQWVMaXlXaEw0UlZGZgpWa2czV0pOMzh3MmlCQk4rb1NVRFhDVk9XMjQ4c3VKWVVaY08rcUJFYzkraEgwR0hubWxWM29TZWxOOGFtTVpyClQxdWdCSXVJR1UwTU5aTzBsb2tkVktSWjVjTDRFYVlLSGM3clJTaGNpZ1NITUNnQXlxOXZEY2pWa0VwNnNackYKUFBpTWN2RkZZL015VTc5a21RN3FUeGJkZThHT0tTU3VDM1R3UXo0VnQrdVRIRi9sb2g5QlRuZTNyWmUzMVQ5bQo3MkNDd29WZG9QQTNYa2dMcENwWCtGSjA5TjBhZm44UW40SnZiMXAvWjAyL3ZqempjMys0N1UxcnZaVFVZWDVPCm80MmZMT1FmUzBPejB1TGtQVFdCVVZkUmhhQ3lvNDBSeFozTi9QNDI0eHVPNFpoQWV0L3psMklUSjA1WVF1QVcKdlJqYytpVUNnWUVBMUdZL2ZUc0Fudm9qZXRaNjFWMWV5dW1qNkt3T3FBeVRXUWpReGZIRk1DOEkvdlZLTWtuagpsN2laVnptZFFOYzMrSjl2L01xaDIyemIzUGM1NzBmNmVBT3dubzNVeDFTQk8rUlgyTEZuMzAyMXZmdEFpZi9SCkNDZVI4cWhZRDdVTEhaeE9oekpCeS9tTFlWcUF3aWc4aDBWVnA3eG8yZkViOUJVcjdxanhObHNDZ1lFQTB5NEoKUDI0UzFrQS81OFFtWlZSaVVnYkpoYXZkY3lVbzJqTUd6K0g4NS9sZjhadis2ODczczFRWHp3cFA5UTE3V2ZuRQpsQ0ZTNTMyYnJUa1BWQXV5cjZJQnhySEZlTm8wVFV1emZKcVhyU21DNnRONXRud2VpMmd0NnA4NHZ5elgyem8wCkdvamlCN0Z1MHV2ckFuVkl5V3Y1NjFhTDhTMkpHZ251bGIvdEZMY0NnWUVBblU2bEV3RFI4dXhidnF4L0NPdEEKTzVSUyt3NERSTjhiZWtPWExDTmw0Y3ZVSTlCVGhXOUlDcVNEblNCa29STkV0dlNPb25FSTgxUnFlZnI0ZzRtUgpyYlU5NkJBdldQbnpSUUpYTlh3ZTdEbkhzczhVKzUvbGx0NkhyMnFTM2Y4K0hrNFhzdlc3MlhBV2xLSE0zditoCjVLV3JUTEcwRGtZT1ovWU9OWklXSkNVQ2dZRUFoOFpaZThheHBiNjVWN0h0WGNqYlRoRy9ENHBvWHRmTlpEemEKYkYzSDFQMm9paGJaMTVMQi96SlovR3VNN09rMkhhdFFrRXk4bU1sUmhoWGhuUS9JV3RPMmdLRmJiVkFkd0hTagplY0dMTTNJMUVmWFZrMVpKN1NlV0hmc3c2UGxUNzBlRXFHckpxRmlVQUZoZVNMNVBoZkUrUmVqbklMRStMTmVsCjVmSDM1QkVDZ1lFQWd1dEJtUGxrWWYvMkovYlFaNmg4WnlLd25GYytUQy9IZGlVbzhFNjlCdXNiOGxQZXlYencKTHdlQXh2WThkNkNWeC9RTGZPV3h2NVJrQ29vR2xNV2orbmJOWFY3VnZ3V1lGVkdSczM2cldRMnpqZCsxZ1BkOApTQjlUZVNFK0lvU0QwNjRHU01uNml3cGZUWm11NEhCUDZhbTJYSkdaNWpNTXZReVVvMzhDNTFrPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/.internal-dns-e2e/kubernetes/project-b-bootstrap.json b/.internal-dns-e2e/kubernetes/project-b-bootstrap.json new file mode 100644 index 00000000..f3cd22b3 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-b-bootstrap.json @@ -0,0 +1,946 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "project-e2e" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "compute-system" + } + }, + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "network-system" + } + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "v1", + "automountServiceAccountToken": false, + "kind": "ServiceAccount", + "metadata": { + "name": "internal-dns-fleet", + "namespace": "internal-dns-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-vpc-integration", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status", + "dnsmanagednamespaces", + "dnsmanagednamespaces/status" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-product-writer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations", + "dnsrecordcontributions" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants", + "dnsresolvercontexts", + "dnsmanagednamespaces", + "dnsnamingpolicies" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsrecordcontributions/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-customer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsnamingpolicies", + "dnsregistrations" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolvercontexts", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-grant-issuer", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsregistrations" + ], + "verbs": [ + "get" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnscontributiongrants" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "internal-dns-control-plane" + }, + "rules": [ + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnsrecordsets", + "dnszoneassociations", + "dnsregistrations", + "dnscontributiongrants", + "dnsrecordcontributions", + "dnsnamingpolicies", + "dnsresolvercontexts", + "dnsresolveraccessbindings" + ], + "verbs": [ + "get", + "list", + "watch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnszones", + "dnszoneassociations" + ], + "verbs": [ + "create", + "patch" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings", + "dnspublicationmanifests", + "dnspublicationchunks", + "dnstransportoutboxes", + "dnspublicationownerships", + "dnsmanagednamespaces" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch", + "delete" + ] + }, + { + "apiGroups": [ + "dns.networking.miloapis.com" + ], + "resources": [ + "dnsresolverbindings/status", + "dnspublicationmanifests/status", + "dnstransportoutboxes/status", + "dnspublicationownerships/status", + "dnsmanagednamespaces/status", + "dnszones/status", + "dnszoneassociations/status", + "dnsnamingpolicies/status", + "dnsregistrations/status", + "dnscontributiongrants/status", + "dnsrecordcontributions/status", + "dnsresolvercontexts/status", + "dnsresolveraccessbindings/status" + ], + "verbs": [ + "get", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + }, + { + "apiGroups": [ + "" + ], + "resources": [ + "configmaps" + ], + "verbs": [ + "get", + "list", + "watch", + "create", + "update", + "patch" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "internal-dns-source-worker", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "internal-dns-control-plane" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-customer", + "namespace": "project-e2e" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-customer", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-customer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-customer-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-b" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-customer-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-customer-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-customer-self", + "namespace": "project-e2e" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-customer" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-customer-self", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-customer-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-customer", + "namespace": "project-e2e" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-publisher", + "namespace": "compute-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-publisher", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-product-writer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-publisher-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-b" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-publisher-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-publisher-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-publisher-self", + "namespace": "compute-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-publisher" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-publisher-self", + "namespace": "compute-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-publisher-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-publisher", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-grant-issuer", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-grant-issuer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-grant-issuer-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-b" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-grant-issuer-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-grant-issuer-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-grant-issuer-self", + "namespace": "compute-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-grant-issuer" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-grant-issuer-self", + "namespace": "compute-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-grant-issuer-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-grant-issuer", + "namespace": "compute-system" + } + ] + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "dns-vpc-integration", + "namespace": "network-system" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-vpc-integration", + "namespace": "project-e2e" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-vpc-integration" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "dns-vpc-integration-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-b" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "dns-vpc-integration-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "dns-vpc-integration-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "dns-vpc-integration-self", + "namespace": "network-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "dns-vpc-integration" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "dns-vpc-integration-self", + "namespace": "network-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "dns-vpc-integration-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "dns-vpc-integration", + "namespace": "network-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "name": "internal-dns-control-plane-parent" + }, + "rules": [ + { + "apiGroups": [ + "authentication.k8s.io" + ], + "resourceNames": [ + "project-b" + ], + "resources": [ + "userextras/iam.miloapis.com/parent-name" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "name": "internal-dns-control-plane-parent" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "internal-dns-control-plane-parent" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "name": "internal-dns-control-plane-self", + "namespace": "internal-dns-system" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "resourceNames": [ + "internal-dns-control-plane" + ], + "resources": [ + "serviceaccounts" + ], + "verbs": [ + "impersonate" + ] + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "name": "internal-dns-control-plane-self", + "namespace": "internal-dns-system" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "internal-dns-control-plane-self" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "internal-dns-control-plane", + "namespace": "internal-dns-system" + } + ] + }, + { + "apiVersion": "dns.networking.miloapis.com/v1alpha1", + "kind": "DNSZoneClass", + "metadata": { + "labels": { + "internal-dns.datum.net/qualification": "true" + }, + "name": "private-bind" + }, + "spec": { + "controllerName": "bind", + "nameServerPolicy": { + "mode": "Static", + "static": { + "servers": [ + "ns.internal." + ] + } + } + } + } + ], + "kind": "List" +} diff --git a/.internal-dns-e2e/kubernetes/project-b-internal.kubeconfig b/.internal-dns-e2e/kubernetes/project-b-internal.kubeconfig new file mode 100644 index 00000000..11f2efe0 --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-b-internal.kubeconfig @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUWNCS2pycFlIZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalU1TVRaYUZ3MHpOakV3TURreU16QTBNVFphTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURpTW8zeWwwY1BDTU1vUUsyMzVMdmhXaTRLZGJoTHJITVR6RzIyNGtOL2R1eVgxeTBsRnlGdDFtOXcKTG1qa2N6YVptR21ERnZXV0ZPRUEwdlBoTmw0OENacERaKzBvUzVGYTVlNCs0dmNPa0tOdHBYOVVWQ0V6TkJhSgptRzZQN0cwVTI2QmlkUWEyVkN6SGVvMjZUam9Uczd3cFduT2NpY2RydlV3Z2F3OFVOSmRSQzl0ek8rUytlV1YvCkFVSk4wbmd3dEordjg0TlV4THhaWitkajUyK1BydlQwN2tPQmJNd2gzRGhFVElBYW9hSHgzaUQwRStzUk9JbWEKblU1RHJlNnJZRW9VL1hOWXd3dVdoVTZYR1FGWDR3ZVN1WGZVTU5jelloUWhJaFZyeWMwUGV6MEtvTjR5amZqTApQZjR6SGJIRTYzT2l2a0UwYzNJN1VuYjl5UzNyQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRRWZIdE5TRlJOU24wMDQ2S3JkTC8yc2NWSm5qQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVRXcVA3WUxKMwpWWkl2aEhiZGRNdDQvak9pMUt2bkx2b3NSSVVqLzZoNkZoMzZVNHFrVkZOdFF3eVM4K2JnMTBDbmpnMkcwZml5CjllQnNSV1RDYVFTNnBmekZScXA0U3gvQURFaDJsWGNkeFVuNTNwWHk5NE4xTmQxTnh0S2IzbVNBdGRJREpraHIKNE5kVEFwSFRxN3lrMjJzT2pFc0NlSzBPQ2UrWU9BTktCZVczNjhyemNjek45SFVKM1RNczRLdUo2Q2dVeTJiego4YzBzcjl2T1BlNzFCWDBRUFJwNk5ZUXkzcFp4U3NCK3ppeWp3eDhrYkRxM2IzVmYyNXNaUlhkZkpLbG1OZXRJClNVN3g3aGlBZ0FWQnJ5Vk55MVBPSjdMbXZGYnNIdHNNTnF2WlVreVhBQW1TSVM0SExLSFQyR3hLRHFlWmlVd0kKUjVkMnFLNEk4OVhoCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://dns-edge-control-plane:6443 + name: kind-dns-edge +contexts: +- context: + cluster: kind-dns-edge + user: kind-dns-edge + name: kind-dns-edge +current-context: kind-dns-edge +kind: Config +users: +- name: kind-dns-edge + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJS2VUdmVtajlIRjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalU1TVRaYUZ3MHlOekV3TVRJeU16QTBNVFphTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDMjRKUHgKY2w0WklmUnRxK0U3WWwvbTRZY1VMSDhtMHY2UEF6UkNVdis0WVZiTzZRcE1IbGJLd2d2SkczaFNsVDZNaHBaQgozV1FhWGFHQUtzRkxPR0NGaDdPL2tTb1FtLzBrdHlEQkE3dTAvdE84OGFsak9kZjI0VDhIeThZZ1R4bGdHbmE1ClFFa0pUK3JnSHh4cklqK3dVc3ZNRDJEVDdZcUttZlprNDUxUmFyRmFqT2RNbnBFWUk1TCtwRDk0VjFBZDFaUWwKcVFFNXV3bTd0aUFIYXVodkNMK3VBUGgyYmF1a1dEL01VM3dZMWhJRlZDeGlXejBSQXhnYnJvWTBJRU5wR2YxKwpIei9lNHloY1hsT0xkdDl4OTZQVzdwYUg2K28vNW53UWoxcktQanZmSGl6bGtmRzZuMVl5azdtWmZqbnZvdERtCnB2MTJvbHpVWVhibU82WUpBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkFSOGUwMUlWRTFLZlRUagpvcXQwdi9heHhVbWVNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJBSGVFN1k5S3ZGTVQ3bTdNa28xcTdvUTFJCmM0TFJrS0Fmcm40UEZmR2V0NU85dFdDYVkwRlNJRXNNL2pMMkN4VXpJU1kzZjk3SE9xV2MrSEdYMUFiWitnMzMKSEVJTUU1QzJKaTVlbVRtblVwaHVnUkFuWlVTQ2lybkNxaWNlY0ZMR1FNQ2dBVWUwQVo3MU1WQWpkcDRvYzZ2YQo3R0xBSERudWVXSCs5MExuUXFCdnlEbjNTeUx3dDl3M1lFa3VEUkxXNlhmVGZxZW91bXZmT2E5Y1dkNW1XT3ZrCmhUQW43Y1lKMlZYdU9Vdzl6cUZUNWpXdEphOThrbEF3VWNoU09lMnBoa1FhVGlQbys2eVI2UWFhUGJuVGlkUDcKK3VSVVlrbUVZc1dSZmoxVEFOMkhWR05XdWhGTGtIcktBV0pEaVphckNsc1p4OXBrTkd3Z2tndVlvQXRYCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdHVDVDhYSmVHU0gwYmF2aE8ySmY1dUdIRkN4L0p0TCtqd00wUWxML3VHRld6dWtLClRCNVd5c0lMeVJ0NFVwVStqSWFXUWQxa0dsMmhnQ3JCU3poZ2hZZXp2NUVxRUp2OUpMY2d3UU83dFA3VHZQR3AKWXpuWDl1RS9COHZHSUU4WllCcDJ1VUJKQ1UvcTRCOGNheUkvc0ZMTHpBOWcwKzJLaXBuMlpPT2RVV3F4V296bgpUSjZSR0NPUy9xUS9lRmRRSGRXVUpha0JPYnNKdTdZZ0Iycm9id2kvcmdENGRtMnJwRmcvekZOOEdOWVNCVlFzCllsczlFUU1ZRzY2R05DQkRhUm45Zmg4LzN1TW9YRjVUaTNiZmNmZWoxdTZXaCt2cVArWjhFSTlheWo0NzN4NHMKNVpIeHVwOVdNcE81bVg0NTc2TFE1cWI5ZHFKYzFHRjI1anVtQ1FJREFRQUJBb0lCQUU1OCtaSDlhMnh1K2NpZQpKZGRHWS9Ha0pzVGVTaDVBSFZnRTRJZDh0dHNNRCtWUytJUHQyNE0vSUM3S2x1d2V1TTg0TGtJTGdseWNoZVdMCnRCVVVGNzBIbm5wZ0U4VTRKYi85eHRXQVJDNHMxblIzNWFLOEh6Z0t1WElmbjZPYlAwRlRhbFFjeHB2MERGTHYKbDFOVnlpN2pnaXROQ0ZvOHo2S243S3VsVXc0dk9oWFBjK2lPR2svODdNNlV6Mm1lais1VWhuQkhKWXNuQlI4MQpPR1l6WE9QeHpYQzdFSGxpZ3VIOU0rQ1FONTZ5NFFkTDZSZWQycDdlMW4vS3QwajdzdHJMeWMvUC9hZmdXaWpICnVkQW9oaEhuZENZL2w5bHlGVnpPYktDWmtLeWJyTXZteStWWVI4QzlyMCs2RXBlaTBlV1k2blZCSHAxRWhLOWMKYjE0K2pUY0NnWUVBejRKRnQzRVBMaTgrUmpZdTdzZWVEUGZCNVBkeWJ4Uk5RbE51OWRCcHlJS0NLWmM2VU1TQwp4eVRoWGQwaVNxbzNxK3RGMVR4Sk8rNTRsVllITS9pVjJ5a0VSZVAwSzN0TU1XRWxqdjBLTTZvZkMyZ3g4SWhUCnFyTVlTemUwUkNDV0txcFZoRmFDajFYTVUzWjNzb3I2ajhrZHBYbitmc21qbWg3ZnN1eEFUZ2NDZ1lFQTRaekcKK1MwaHBJb0o0b3JNdDFQZDRzd3Y2MEsvN2xobzZXZFJ1TGlRcjEwc0V1TWljT0hYVngzV1dUY05MNjBodm1wdQpYLy9sR3VGNjRET1M3dkJieTdxVVZ2RzlSRThsMmlJWmQ1bXozN1J5T2hBT3ArUWk2UGI0aHRHZVVnakNNR3MxCm5RRFlKL1ZCVWIvZzV1R2RBSy85V1VhYjZLVUF2c0JCSzdRaloyOENnWUJhZVRRb1hSVC9ULzNZNHZybmhRVTIKSmpmVnVzV2NCTUFDc1JucDl5cUlEODdzajFFUHNmNUxFWnRwTDJjcUlya1BMZzlPZjNCMnZ1eTRQNy80T3VRcgpuK0NMRUc1L0hzMERvcFdibUZ3QVZxWWVkUm1CQkE5bWw1dDZBQm1Lc3FLR2k2bXBmNGFCTkNsWUlvamh3WktwCmFraVF3M1hnbGMzTS9yVU43OC9CdFFLQmdGUVErd00yRWVjaG1IVlZpdHAwNGFUMHVKWk5GUDlXNmlDdVl3RVYKcExQelg4elozYWQ2UDZjeWN3QmpSd0tRM0pYN1NvMXk2SWVQcFIyTm1oNTJhWDRNVStIaHljaCtSWGgyWkU3dwpjdG5VZnd1cm1BcHZNNkxqQTlBWW80bWx3ZFlRM0xWWDd6TlVhN1p1YlBzYTdrbW5TaHZOOTZKRWVYYWFaSzVJClBUajVBb0dCQUxERnRpWHFXMElIVnA3TWdpaFRGQm8wS2FraUhLQ25tdlluWkNRWStrZ3VZY21kR3RFWmh4K0EKTi9LdG54RDA2WGRteCszOW5aRDdNaU5DZlYzWS9IamhqV2laVjlpeUJ4WFdidjVwRTd0V0xKaEJvdmdJUU5aMgp4bnFlTk5ybmlCVVFZQ2owTHg5SHhiOHg2SmpmTlREZTNxbGg0YlZKYXllNGkxUmlYMXdyCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== + diff --git a/.internal-dns-e2e/kubernetes/project-b.kubeconfig b/.internal-dns-e2e/kubernetes/project-b.kubeconfig new file mode 100644 index 00000000..2ea48d3e --- /dev/null +++ b/.internal-dns-e2e/kubernetes/project-b.kubeconfig @@ -0,0 +1,18 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUWNCS2pycFlIZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalU1TVRaYUZ3MHpOakV3TURreU16QTBNVFphTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURpTW8zeWwwY1BDTU1vUUsyMzVMdmhXaTRLZGJoTHJITVR6RzIyNGtOL2R1eVgxeTBsRnlGdDFtOXcKTG1qa2N6YVptR21ERnZXV0ZPRUEwdlBoTmw0OENacERaKzBvUzVGYTVlNCs0dmNPa0tOdHBYOVVWQ0V6TkJhSgptRzZQN0cwVTI2QmlkUWEyVkN6SGVvMjZUam9Uczd3cFduT2NpY2RydlV3Z2F3OFVOSmRSQzl0ek8rUytlV1YvCkFVSk4wbmd3dEordjg0TlV4THhaWitkajUyK1BydlQwN2tPQmJNd2gzRGhFVElBYW9hSHgzaUQwRStzUk9JbWEKblU1RHJlNnJZRW9VL1hOWXd3dVdoVTZYR1FGWDR3ZVN1WGZVTU5jelloUWhJaFZyeWMwUGV6MEtvTjR5amZqTApQZjR6SGJIRTYzT2l2a0UwYzNJN1VuYjl5UzNyQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRRWZIdE5TRlJOU24wMDQ2S3JkTC8yc2NWSm5qQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVRXcVA3WUxKMwpWWkl2aEhiZGRNdDQvak9pMUt2bkx2b3NSSVVqLzZoNkZoMzZVNHFrVkZOdFF3eVM4K2JnMTBDbmpnMkcwZml5CjllQnNSV1RDYVFTNnBmekZScXA0U3gvQURFaDJsWGNkeFVuNTNwWHk5NE4xTmQxTnh0S2IzbVNBdGRJREpraHIKNE5kVEFwSFRxN3lrMjJzT2pFc0NlSzBPQ2UrWU9BTktCZVczNjhyemNjek45SFVKM1RNczRLdUo2Q2dVeTJiego4YzBzcjl2T1BlNzFCWDBRUFJwNk5ZUXkzcFp4U3NCK3ppeWp3eDhrYkRxM2IzVmYyNXNaUlhkZkpLbG1OZXRJClNVN3g3aGlBZ0FWQnJ5Vk55MVBPSjdMbXZGYnNIdHNNTnF2WlVreVhBQW1TSVM0SExLSFQyR3hLRHFlWmlVd0kKUjVkMnFLNEk4OVhoCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://127.0.0.1:63980 + name: kind-dns-edge +contexts: +- context: + cluster: kind-dns-edge + user: kind-dns-edge + name: kind-dns-edge +current-context: kind-dns-edge +kind: Config +users: +- name: kind-dns-edge + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJS2VUdmVtajlIRjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpFd01USXlNalU1TVRaYUZ3MHlOekV3TVRJeU16QTBNVFphTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDMjRKUHgKY2w0WklmUnRxK0U3WWwvbTRZY1VMSDhtMHY2UEF6UkNVdis0WVZiTzZRcE1IbGJLd2d2SkczaFNsVDZNaHBaQgozV1FhWGFHQUtzRkxPR0NGaDdPL2tTb1FtLzBrdHlEQkE3dTAvdE84OGFsak9kZjI0VDhIeThZZ1R4bGdHbmE1ClFFa0pUK3JnSHh4cklqK3dVc3ZNRDJEVDdZcUttZlprNDUxUmFyRmFqT2RNbnBFWUk1TCtwRDk0VjFBZDFaUWwKcVFFNXV3bTd0aUFIYXVodkNMK3VBUGgyYmF1a1dEL01VM3dZMWhJRlZDeGlXejBSQXhnYnJvWTBJRU5wR2YxKwpIei9lNHloY1hsT0xkdDl4OTZQVzdwYUg2K28vNW53UWoxcktQanZmSGl6bGtmRzZuMVl5azdtWmZqbnZvdERtCnB2MTJvbHpVWVhibU82WUpBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkFSOGUwMUlWRTFLZlRUagpvcXQwdi9heHhVbWVNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJBSGVFN1k5S3ZGTVQ3bTdNa28xcTdvUTFJCmM0TFJrS0Fmcm40UEZmR2V0NU85dFdDYVkwRlNJRXNNL2pMMkN4VXpJU1kzZjk3SE9xV2MrSEdYMUFiWitnMzMKSEVJTUU1QzJKaTVlbVRtblVwaHVnUkFuWlVTQ2lybkNxaWNlY0ZMR1FNQ2dBVWUwQVo3MU1WQWpkcDRvYzZ2YQo3R0xBSERudWVXSCs5MExuUXFCdnlEbjNTeUx3dDl3M1lFa3VEUkxXNlhmVGZxZW91bXZmT2E5Y1dkNW1XT3ZrCmhUQW43Y1lKMlZYdU9Vdzl6cUZUNWpXdEphOThrbEF3VWNoU09lMnBoa1FhVGlQbys2eVI2UWFhUGJuVGlkUDcKK3VSVVlrbUVZc1dSZmoxVEFOMkhWR05XdWhGTGtIcktBV0pEaVphckNsc1p4OXBrTkd3Z2tndVlvQXRYCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBdHVDVDhYSmVHU0gwYmF2aE8ySmY1dUdIRkN4L0p0TCtqd00wUWxML3VHRld6dWtLClRCNVd5c0lMeVJ0NFVwVStqSWFXUWQxa0dsMmhnQ3JCU3poZ2hZZXp2NUVxRUp2OUpMY2d3UU83dFA3VHZQR3AKWXpuWDl1RS9COHZHSUU4WllCcDJ1VUJKQ1UvcTRCOGNheUkvc0ZMTHpBOWcwKzJLaXBuMlpPT2RVV3F4V296bgpUSjZSR0NPUy9xUS9lRmRRSGRXVUpha0JPYnNKdTdZZ0Iycm9id2kvcmdENGRtMnJwRmcvekZOOEdOWVNCVlFzCllsczlFUU1ZRzY2R05DQkRhUm45Zmg4LzN1TW9YRjVUaTNiZmNmZWoxdTZXaCt2cVArWjhFSTlheWo0NzN4NHMKNVpIeHVwOVdNcE81bVg0NTc2TFE1cWI5ZHFKYzFHRjI1anVtQ1FJREFRQUJBb0lCQUU1OCtaSDlhMnh1K2NpZQpKZGRHWS9Ha0pzVGVTaDVBSFZnRTRJZDh0dHNNRCtWUytJUHQyNE0vSUM3S2x1d2V1TTg0TGtJTGdseWNoZVdMCnRCVVVGNzBIbm5wZ0U4VTRKYi85eHRXQVJDNHMxblIzNWFLOEh6Z0t1WElmbjZPYlAwRlRhbFFjeHB2MERGTHYKbDFOVnlpN2pnaXROQ0ZvOHo2S243S3VsVXc0dk9oWFBjK2lPR2svODdNNlV6Mm1lais1VWhuQkhKWXNuQlI4MQpPR1l6WE9QeHpYQzdFSGxpZ3VIOU0rQ1FONTZ5NFFkTDZSZWQycDdlMW4vS3QwajdzdHJMeWMvUC9hZmdXaWpICnVkQW9oaEhuZENZL2w5bHlGVnpPYktDWmtLeWJyTXZteStWWVI4QzlyMCs2RXBlaTBlV1k2blZCSHAxRWhLOWMKYjE0K2pUY0NnWUVBejRKRnQzRVBMaTgrUmpZdTdzZWVEUGZCNVBkeWJ4Uk5RbE51OWRCcHlJS0NLWmM2VU1TQwp4eVRoWGQwaVNxbzNxK3RGMVR4Sk8rNTRsVllITS9pVjJ5a0VSZVAwSzN0TU1XRWxqdjBLTTZvZkMyZ3g4SWhUCnFyTVlTemUwUkNDV0txcFZoRmFDajFYTVUzWjNzb3I2ajhrZHBYbitmc21qbWg3ZnN1eEFUZ2NDZ1lFQTRaekcKK1MwaHBJb0o0b3JNdDFQZDRzd3Y2MEsvN2xobzZXZFJ1TGlRcjEwc0V1TWljT0hYVngzV1dUY05MNjBodm1wdQpYLy9sR3VGNjRET1M3dkJieTdxVVZ2RzlSRThsMmlJWmQ1bXozN1J5T2hBT3ArUWk2UGI0aHRHZVVnakNNR3MxCm5RRFlKL1ZCVWIvZzV1R2RBSy85V1VhYjZLVUF2c0JCSzdRaloyOENnWUJhZVRRb1hSVC9ULzNZNHZybmhRVTIKSmpmVnVzV2NCTUFDc1JucDl5cUlEODdzajFFUHNmNUxFWnRwTDJjcUlya1BMZzlPZjNCMnZ1eTRQNy80T3VRcgpuK0NMRUc1L0hzMERvcFdibUZ3QVZxWWVkUm1CQkE5bWw1dDZBQm1Lc3FLR2k2bXBmNGFCTkNsWUlvamh3WktwCmFraVF3M1hnbGMzTS9yVU43OC9CdFFLQmdGUVErd00yRWVjaG1IVlZpdHAwNGFUMHVKWk5GUDlXNmlDdVl3RVYKcExQelg4elozYWQ2UDZjeWN3QmpSd0tRM0pYN1NvMXk2SWVQcFIyTm1oNTJhWDRNVStIaHljaCtSWGgyWkU3dwpjdG5VZnd1cm1BcHZNNkxqQTlBWW80bWx3ZFlRM0xWWDd6TlVhN1p1YlBzYTdrbW5TaHZOOTZKRWVYYWFaSzVJClBUajVBb0dCQUxERnRpWHFXMElIVnA3TWdpaFRGQm8wS2FraUhLQ25tdlluWkNRWStrZ3VZY21kR3RFWmh4K0EKTi9LdG54RDA2WGRteCszOW5aRDdNaU5DZlYzWS9IamhqV2laVjlpeUJ4WFdidjVwRTd0V0xKaEJvdmdJUU5aMgp4bnFlTk5ybmlCVVFZQ2owTHg5SHhiOHg2SmpmTlREZTNxbGg0YlZKYXllNGkxUmlYMXdyCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/api/v1alpha1/internaldns_access_test.go b/api/v1alpha1/internaldns_access_test.go new file mode 100644 index 00000000..887b0234 --- /dev/null +++ b/api/v1alpha1/internaldns_access_test.go @@ -0,0 +1,17 @@ +package v1alpha1 + +import ( + "encoding/json" + "testing" +) + +func TestResolverAccessHasNoExpiryOrClusterIdentity(t *testing.T) { + a := DNSResolverAccessVersion{Epoch: 1, Sequence: 2} + b, err := json.Marshal(a) + if err != nil { + t.Fatal(err) + } + if string(b) != `{"epoch":1,"sequence":2}` { + t.Fatalf("unexpected access fence: %s", b) + } +} diff --git a/api/v1alpha1/internaldns_context_boundary_envtest_test.go b/api/v1alpha1/internaldns_context_boundary_envtest_test.go new file mode 100644 index 00000000..2c099c0c --- /dev/null +++ b/api/v1alpha1/internaldns_context_boundary_envtest_test.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package v1alpha1 + +import ( + "context" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func validateResolverContextBoundaries(t *testing.T, cl client.Client) { + t.Helper() + for _, kind := range []string{"DNSZoneAssociation", "DNSNamingPolicy"} { + t.Run(kind+"-context-boundary", func(t *testing.T) { + newObject := func(name string) *unstructured.Unstructured { + return &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": GroupVersion.String(), "kind": kind, + "metadata": map[string]any{"name": name, "namespace": "validation"}, + "spec": map[string]any{ + "dnsZoneRef": map[string]any{"name": "zone", "uid": "zone-uid"}, + "additionalNames": []any{}, + }, + }} + } + ctx := context.Background() + for _, ref := range []map[string]any{nil, {"name": ""}, {"name": "", "uid": "context-uid"}} { + o := newObject("context-required") + if ref != nil { + if err := unstructured.SetNestedMap(o.Object, ref, "spec", "resolverContextRef"); err != nil { + t.Fatal(err) + } + } + if err := unstructured.SetNestedMap(o.Object, map[string]any{"name": "network", "uid": "network-uid"}, "spec", "vpcRef"); err != nil { + t.Fatal(err) + } + if err := cl.Create(ctx, o); !apierrors.IsInvalid(err) { + t.Fatalf("expected rejection without a named DNS context, got %v", err) + } + } + o := newObject("context-selected") + if err := unstructured.SetNestedMap(o.Object, map[string]any{"name": "context", "uid": "context-uid"}, "spec", "resolverContextRef"); err != nil { + t.Fatal(err) + } + if err := unstructured.SetNestedMap(o.Object, map[string]any{"name": "network", "uid": "network-uid"}, "spec", "vpcRef"); err != nil { + t.Fatal(err) + } + if err := cl.Create(ctx, o); err != nil { + t.Fatal(err) + } + if err := cl.Get(ctx, client.ObjectKeyFromObject(o), o); err != nil { + t.Fatal(err) + } + if _, found, err := unstructured.NestedMap(o.Object, "spec", "vpcRef"); err != nil || found { + t.Fatalf("network reference was retained in the DNS API: found=%t err=%v", found, err) + } + if err := unstructured.SetNestedMap(o.Object, map[string]any{"name": "", "uid": "context-uid"}, "spec", "resolverContextRef"); err != nil { + t.Fatal(err) + } + if err := cl.Update(ctx, o); !apierrors.IsInvalid(err) { + t.Fatalf("expected rejection when clearing the DNS context, got %v", err) + } + }) + } +} diff --git a/api/v1alpha1/internaldns_project_types.go b/api/v1alpha1/internaldns_project_types.go new file mode 100644 index 00000000..3792af66 --- /dev/null +++ b/api/v1alpha1/internaldns_project_types.go @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +// DNSObjectReference pins a namespaced object by API-assigned UID. Generation +// is used where a writer must also be fenced against policy changes. +type DNSObjectReference struct { + // +kubebuilder:validation:MaxLength=253 + Name string `json:"name"` + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:MaxLength=128 + UID types.UID `json:"uid,omitempty"` + // +kubebuilder:validation:Minimum=0 + Generation int64 `json:"generation,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="!has(self.resolverContextRef) || ((self.resolverContextRef.name.size() == 0 && !has(self.resolverContextRef.uid) && (!has(self.resolverContextRef.generation) || self.resolverContextRef.generation == 0)) || (self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() > 0))",message="a nonempty resolverContextRef must pin name and UID" +// +kubebuilder:validation:XValidation:rule="has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0",message="dnsZoneRef must pin the zone UID" +// +kubebuilder:validation:XValidation:rule="self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() > 0",message="resolverContextRef is required" +type DNSZoneAssociationSpec struct { + DNSZoneRef DNSObjectReference `json:"dnsZoneRef"` + ResolverContextRef DNSObjectReference `json:"resolverContextRef"` + Managed bool `json:"managed,omitempty"` +} + +type DNSZoneAssociationStatus struct { + ResolvedDNSZoneRef DNSObjectReference `json:"resolvedDNSZoneRef,omitempty"` + ResolvedResolverContextRef DNSObjectReference `json:"resolvedResolverContextRef,omitempty"` + BindingRef *DNSObjectReference `json:"bindingRef,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// DNSResolverContext is the DNS-owned serving and namespace boundary created by +// a trusted network integration. consumerID is opaque to DNS and immutable. +// +kubebuilder:validation:XValidation:rule="oldSelf.consumerID == self.consumerID",message="consumerID is immutable" +type DNSResolverContextSpec struct { + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=512 + ConsumerID string `json:"consumerID"` + ManagedNamespace DNSResolverContextManagedNamespace `json:"managedNamespace,omitempty"` +} + +type DNSResolverContextManagedNamespace struct { + Enabled bool `json:"enabled,omitempty"` +} + +type DNSResolverContextManagedNamespaceStatus struct { + DNSZoneRef DNSObjectReference `json:"dnsZoneRef,omitempty"` + Suffix string `json:"suffix,omitempty"` +} + +type DNSResolverContextStatus struct { + ManagedNamespace DNSResolverContextManagedNamespaceStatus `json:"managedNamespace,omitempty"` + ServingTargets []DNSResolverContextServingTarget `json:"servingTargets,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +type DNSResolverContextServingTarget struct { + Region string `json:"region"` + Shard string `json:"shard"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:annotations="discovery.miloapis.com/parent-contexts=Project" +type DNSResolverContext struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSResolverContextSpec `json:"spec"` + Status DNSResolverContextStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSResolverContextList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSResolverContext `json:"items"` +} + +type DNSResolverAccessVersion struct { + // +kubebuilder:validation:Minimum=1 + Epoch int64 `json:"epoch"` + // +kubebuilder:validation:Minimum=1 + Sequence int64 `json:"sequence"` +} + +// +kubebuilder:validation:XValidation:rule="self.contextRef.name.size() > 0 && has(self.contextRef.uid) && self.contextRef.uid.size() > 0",message="contextRef requires name and UID" +// +kubebuilder:validation:XValidation:rule="self.contextRef == oldSelf.contextRef",message="contextRef is immutable" +// +kubebuilder:validation:XValidation:rule="self.region == oldSelf.region && self.destinationAddress == oldSelf.destinationAddress",message="access destination is immutable" +type DNSResolverAccessBindingSpec struct { + ContextRef DNSObjectReference `json:"contextRef"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + Region string `json:"region"` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:XValidation:rule="isIP(self)",message="destinationAddress must be an IP address" + DestinationAddress string `json:"destinationAddress"` +} + +type DNSResolverAccessBindingStatus struct { + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + BindingRef *DNSObjectReference `json:"bindingRef,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:annotations="discovery.miloapis.com/parent-contexts=Project" +type DNSResolverAccessBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSResolverAccessBindingSpec `json:"spec"` + Status DNSResolverAccessBindingStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSResolverAccessBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSResolverAccessBinding `json:"items"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:annotations="discovery.miloapis.com/parent-contexts=Project" +type DNSZoneAssociation struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSZoneAssociationSpec `json:"spec"` + Status DNSZoneAssociationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSZoneAssociationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSZoneAssociation `json:"items"` +} + +const DNSRegistrationMaxTTLSeconds int32 = 30 + +// +kubebuilder:validation:XValidation:rule="has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0",message="dnsZoneRef must pin the zone UID" +type DNSRegistrationSpec struct { + DNSZoneRef DNSObjectReference `json:"dnsZoneRef"` + Name string `json:"name"` + RecordTypes []RRType `json:"recordTypes"` + // TTLSeconds bounds cached dynamic answers to at most 30 seconds after withdrawal. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=30 + TTLSeconds int32 `json:"ttlSeconds"` + ReservedDescendants []string `json:"reservedDescendants,omitempty"` +} + +type DNSRegistrationStatus struct { + CanonicalFQDN string `json:"canonicalFQDN,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + PublicationWriterEpoch int64 `json:"publicationWriterEpoch,omitempty"` + PublicationRevision int64 `json:"publicationRevision,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:annotations="discovery.miloapis.com/parent-contexts=Project" +type DNSRegistration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSRegistrationSpec `json:"spec"` + Status DNSRegistrationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSRegistrationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSRegistration `json:"items"` +} + +type DNSContributionRecordSet struct { + RecordType RRType `json:"recordType"` + Records []RecordEntry `json:"records"` +} + +// +kubebuilder:validation:XValidation:rule="self.registrationRef.name.size() > 0 && has(self.registrationRef.uid) && self.registrationRef.uid.size() > 0 && has(self.registrationRef.generation) && self.registrationRef.generation > 0",message="registrationRef requires name, UID, and generation" +// +kubebuilder:validation:XValidation:rule="self.registrationRef.name == oldSelf.registrationRef.name && self.registrationRef.uid == oldSelf.registrationRef.uid",message="contribution registration identity is immutable" +type DNSRecordContributionSpec struct { + RegistrationRef DNSObjectReference `json:"registrationRef"` + RecordSets []DNSContributionRecordSet `json:"recordSets"` + Eligible bool `json:"eligible"` + Reason string `json:"reason,omitempty"` +} + +type DNSRecordContributionStatus struct { + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + PublishedRevision int64 `json:"publishedRevision,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:annotations="discovery.miloapis.com/parent-contexts=Project" +type DNSRecordContribution struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSRecordContributionSpec `json:"spec"` + Status DNSRecordContributionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSRecordContributionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSRecordContribution `json:"items"` +} + +// +kubebuilder:validation:XValidation:rule="!has(self.resolverContextRef) || ((self.resolverContextRef.name.size() == 0 && !has(self.resolverContextRef.uid) && (!has(self.resolverContextRef.generation) || self.resolverContextRef.generation == 0)) || (self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() > 0))",message="a nonempty resolverContextRef must pin name and UID" +// +kubebuilder:validation:XValidation:rule="!has(self.dnsZoneRef) || self.dnsZoneRef.name.size() == 0 || (has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0)",message="a selected dnsZoneRef must pin the zone UID" +// +kubebuilder:validation:XValidation:rule="self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() > 0",message="resolverContextRef is required" +type DNSNamingPolicySpec struct { + ResolverContextRef DNSObjectReference `json:"resolverContextRef"` + AdditionalNames []DNSAdditionalNameRule `json:"additionalNames"` + DNSZoneRef DNSObjectReference `json:"dnsZoneRef,omitempty"` + ScopePrefix string `json:"scopePrefix,omitempty"` + Priority int32 `json:"priority,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0",message="dnsZoneRef must pin the zone UID" +type DNSAdditionalNameRule struct { + Selector *DNSRegistrationSelector `json:"selector,omitempty"` + DNSZoneRef DNSObjectReference `json:"dnsZoneRef"` + NamePrefix string `json:"namePrefix"` +} + +// DNSRegistrationSelector matches registration labels using Kubernetes selector semantics. +type DNSRegistrationSelector struct { + // +kubebuilder:validation:MaxProperties=64 + MatchLabels map[string]string `json:"matchLabels,omitempty"` + // +kubebuilder:validation:MaxItems=64 + MatchExpressions []DNSRegistrationSelectorRequirement `json:"matchExpressions,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="(self.operator in ['In', 'NotIn']) ? (has(self.values) && self.values.size() > 0) : (!has(self.values) || self.values.size() == 0)",message="selector operator requires compatible values" +type DNSRegistrationSelectorRequirement struct { + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=317 + Key string `json:"key"` + // +kubebuilder:validation:Enum=In;NotIn;Exists;DoesNotExist + Operator metav1.LabelSelectorOperator `json:"operator"` + // +kubebuilder:validation:MaxItems=64 + // +kubebuilder:validation:items:MaxLength=63 + // +kubebuilder:validation:items:Pattern=`^([A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$` + Values []string `json:"values,omitempty"` +} + +// AsLabelSelector converts the API selector for Kubernetes label matching. +func (s *DNSRegistrationSelector) AsLabelSelector() *metav1.LabelSelector { + if s == nil { + return nil + } + out := &metav1.LabelSelector{MatchLabels: s.MatchLabels} + for _, requirement := range s.MatchExpressions { + out.MatchExpressions = append(out.MatchExpressions, metav1.LabelSelectorRequirement{ + Key: requirement.Key, Operator: requirement.Operator, Values: requirement.Values, + }) + } + return out +} + +type DNSNamingPolicyStatus struct { + ResolvedResolverContextRef DNSObjectReference `json:"resolvedResolverContextRef,omitempty"` + ResolvedDNSZoneRef DNSObjectReference `json:"resolvedDNSZoneRef,omitempty"` + ResolvedAdditionalNames []DNSResolvedAdditionalNameRule `json:"resolvedAdditionalNames,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +type DNSResolvedAdditionalNameRule struct { + Selector *metav1.LabelSelector `json:"selector,omitempty"` + DNSZoneRef DNSObjectReference `json:"dnsZoneRef"` + NamePrefix string `json:"namePrefix"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type DNSNamingPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec DNSNamingPolicySpec `json:"spec"` + Status DNSNamingPolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type DNSNamingPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DNSNamingPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DNSZoneAssociation{}, &DNSZoneAssociationList{}, &DNSResolverContext{}, &DNSResolverContextList{}, &DNSResolverAccessBinding{}, &DNSResolverAccessBindingList{}, &DNSRegistration{}, &DNSRegistrationList{}, &DNSRecordContribution{}, &DNSRecordContributionList{}, &DNSNamingPolicy{}, &DNSNamingPolicyList{}) +} diff --git a/api/v1alpha1/internaldns_types_test.go b/api/v1alpha1/internaldns_types_test.go new file mode 100644 index 00000000..14fe6d11 --- /dev/null +++ b/api/v1alpha1/internaldns_types_test.go @@ -0,0 +1,30 @@ +package v1alpha1 + +import ( + "encoding/json" + "testing" +) + +func TestDNSPoliciesSelectResolverContexts(t *testing.T) { + for name, value := range map[string]any{ + "association context": DNSZoneAssociationSpec{ResolverContextRef: DNSObjectReference{Name: "ctx", UID: "ctx-uid"}}, + "naming context": DNSNamingPolicySpec{ResolverContextRef: DNSObjectReference{Name: "ctx", UID: "ctx-uid"}}, + } { + t.Run(name, func(t *testing.T) { + data, err := json.Marshal(value) + if err != nil { + t.Fatal(err) + } + var object map[string]any + if err := json.Unmarshal(data, &object); err != nil { + t.Fatal(err) + } + if _, hasVPC := object["vpcRef"]; hasVPC { + t.Fatal("DNS policies must not expose VPC references") + } + if _, hasContext := object["resolverContextRef"]; !hasContext { + t.Fatal("expected value-typed resolverContextRef property") + } + }) + } +} diff --git a/api/v1alpha1/internaldns_validation_envtest_test.go b/api/v1alpha1/internaldns_validation_envtest_test.go new file mode 100644 index 00000000..6fc45c37 --- /dev/null +++ b/api/v1alpha1/internaldns_validation_envtest_test.go @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package v1alpha1 + +import ( + "context" + "os" + "path/filepath" + "testing" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +// These tests exercise the served CRD schemas without an admission webhook. +func TestPrivateDNSValidationAtAPIServer(t *testing.T) { + assets := os.Getenv("KUBEBUILDER_ASSETS") + if assets == "" { + t.Skip("set KUBEBUILDER_ASSETS to test private DNS schemas against the API server") + } + environment := &envtest.Environment{BinaryAssetsDirectory: assets, CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true} + cfg, err := environment.Start() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := environment.Stop(); err != nil { + t.Error(err) + } + }) + scheme := runtime.NewScheme() + if err := AddToScheme(scheme); err != nil { + t.Fatal(err) + } + if err := corev1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + cl, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + if err := cl.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "validation"}}); err != nil { + t.Fatal(err) + } + validateResolverContextBoundaries(t, cl) + requireInvalid := func(t *testing.T, err error) { + t.Helper() + if !apierrors.IsInvalid(err) { + t.Fatalf("expected API validation rejection, got %v", err) + } + } + newAccess := func(name string) *DNSResolverAccessBinding { + return &DNSResolverAccessBinding{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "validation"}, Spec: DNSResolverAccessBindingSpec{ContextRef: DNSObjectReference{Name: "context", UID: "context-uid"}, Region: "central", DestinationAddress: "fd53::1"}} + } + for _, tc := range []struct { + name string + mutate func(*DNSResolverAccessBinding) + }{ + {"empty-context", func(a *DNSResolverAccessBinding) { a.Spec.ContextRef.Name = "" }}, + {"missing-context-uid", func(a *DNSResolverAccessBinding) { a.Spec.ContextRef.UID = "" }}, + {"empty-region", func(a *DNSResolverAccessBinding) { a.Spec.Region = "" }}, + {"empty-address", func(a *DNSResolverAccessBinding) { a.Spec.DestinationAddress = "" }}, + {"invalid-address", func(a *DNSResolverAccessBinding) { a.Spec.DestinationAddress = "resolver.internal" }}, + } { + t.Run(tc.name, func(t *testing.T) { a := newAccess(tc.name); tc.mutate(a); requireInvalid(t, cl.Create(ctx, a)) }) + } + for _, tc := range []struct { + name string + mutate func(*DNSResolverAccessBinding) + }{ + {"change-context", func(a *DNSResolverAccessBinding) { a.Spec.ContextRef.Name = "other" }}, + {"change-region", func(a *DNSResolverAccessBinding) { a.Spec.Region = "east" }}, + {"change-address", func(a *DNSResolverAccessBinding) { a.Spec.DestinationAddress = "fd53::2" }}, + } { + t.Run(tc.name, func(t *testing.T) { + a := newAccess(tc.name) + if err := cl.Create(ctx, a); err != nil { + t.Fatal(err) + } + tc.mutate(a) + requireInvalid(t, cl.Update(ctx, a)) + }) + } + t.Run("metadata-and-status", func(t *testing.T) { + a := newAccess("renewal") + if err := cl.Create(ctx, a); err != nil { + t.Fatal(err) + } + if err := cl.Update(ctx, a); err != nil { + t.Fatal(err) + } + a.Status.ObservedGeneration = a.Generation + if err := cl.Status().Update(ctx, a); err != nil { + t.Fatalf("access status update failed: %v", err) + } + a.Labels = map[string]string{"test": "metadata"} + if err := cl.Update(ctx, a); err != nil { + t.Fatal(err) + } + }) + t.Run("consumer-identity", func(t *testing.T) { + a := &DNSResolverContext{ObjectMeta: metav1.ObjectMeta{Name: "consumer", Namespace: "validation"}, Spec: DNSResolverContextSpec{ConsumerID: ""}} + requireInvalid(t, cl.Create(ctx, a)) + a.Spec.ConsumerID = "network/application" + if err := cl.Create(ctx, a); err != nil { + t.Fatal(err) + } + a.Spec.ConsumerID = "network/other" + requireInvalid(t, cl.Update(ctx, a)) + }) + for _, ttl := range []int32{0, 31, 2147483647} { + t.Run("ttl", func(t *testing.T) { + a := &DNSRegistration{ObjectMeta: metav1.ObjectMeta{GenerateName: "registration-", Namespace: "validation"}, Spec: DNSRegistrationSpec{DNSZoneRef: DNSObjectReference{Name: "zone", UID: "zone-uid"}, Name: "web", RecordTypes: []RRType{RRTypeAAAA}, TTLSeconds: ttl}} + requireInvalid(t, cl.Create(ctx, a)) + }) + } + t.Run("contribution-registration", func(t *testing.T) { + a := &DNSRecordContribution{ObjectMeta: metav1.ObjectMeta{Name: "contribution", Namespace: "validation"}, Spec: DNSRecordContributionSpec{RegistrationRef: DNSObjectReference{Name: "web", UID: "registration-uid", Generation: 1}, RecordSets: []DNSContributionRecordSet{}}} + if err := cl.Create(ctx, a); err != nil { + t.Fatal(err) + } + a.Spec.RegistrationRef.Generation = 2 + if err := cl.Update(ctx, a); err != nil { + t.Fatalf("registration generation cannot advance: %v", err) + } + a.Spec.RegistrationRef.Name = "other" + requireInvalid(t, cl.Update(ctx, a)) + }) + t.Run("atomic-contribution-and-stale-update", func(t *testing.T) { + a := &DNSRecordContribution{ObjectMeta: metav1.ObjectMeta{Name: "observations", Namespace: "validation"}, Spec: DNSRecordContributionSpec{RegistrationRef: DNSObjectReference{Name: "web", UID: "registration-uid", Generation: 1}, RecordSets: []DNSContributionRecordSet{}, Eligible: true}} + if err := cl.Create(ctx, a); err != nil { + t.Fatal(err) + } + stale := a.DeepCopy() + generation := a.Generation + a.Spec.Eligible = false + a.Spec.Reason = "EndpointUnhealthy" + a.Spec.RecordSets = []DNSContributionRecordSet{{RecordType: RRTypeA, Records: []RecordEntry{{Name: "web", A: &ARecordSpec{Content: "10.0.0.9"}}}}} + if err := cl.Update(ctx, a); err != nil { + t.Fatal(err) + } + if a.Generation != generation+1 { + t.Fatalf("atomic desired state did not advance generation: %d", a.Generation) + } + if err := cl.Update(ctx, stale); !apierrors.IsConflict(err) { + t.Fatalf("stale writer was not rejected: %v", err) + } + a.Status.ObservedGeneration = a.Generation + a.Status.Conditions = []metav1.Condition{{Type: "Published", Status: metav1.ConditionTrue, Reason: "Applied", LastTransitionTime: metav1.Now()}} + if err := cl.Status().Update(ctx, a); err != nil { + t.Fatal(err) + } + var observed DNSRecordContribution + if err := cl.Get(ctx, client.ObjectKeyFromObject(a), &observed); err != nil { + t.Fatal(err) + } + if observed.Spec.Eligible || observed.Spec.RecordSets[0].Records[0].A.Content != "10.0.0.9" { + t.Fatal("status write changed producer desired state") + } + }) + +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c86213e2..df717379 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -87,6 +87,282 @@ func (in *CNAMERecordSpec) DeepCopy() *CNAMERecordSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSAdditionalNameRule) DeepCopyInto(out *DNSAdditionalNameRule) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(DNSRegistrationSelector) + (*in).DeepCopyInto(*out) + } + out.DNSZoneRef = in.DNSZoneRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSAdditionalNameRule. +func (in *DNSAdditionalNameRule) DeepCopy() *DNSAdditionalNameRule { + if in == nil { + return nil + } + out := new(DNSAdditionalNameRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSContributionRecordSet) DeepCopyInto(out *DNSContributionRecordSet) { + *out = *in + if in.Records != nil { + in, out := &in.Records, &out.Records + *out = make([]RecordEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSContributionRecordSet. +func (in *DNSContributionRecordSet) DeepCopy() *DNSContributionRecordSet { + if in == nil { + return nil + } + out := new(DNSContributionRecordSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSNamingPolicy) DeepCopyInto(out *DNSNamingPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSNamingPolicy. +func (in *DNSNamingPolicy) DeepCopy() *DNSNamingPolicy { + if in == nil { + return nil + } + out := new(DNSNamingPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSNamingPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSNamingPolicyList) DeepCopyInto(out *DNSNamingPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSNamingPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSNamingPolicyList. +func (in *DNSNamingPolicyList) DeepCopy() *DNSNamingPolicyList { + if in == nil { + return nil + } + out := new(DNSNamingPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSNamingPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSNamingPolicySpec) DeepCopyInto(out *DNSNamingPolicySpec) { + *out = *in + out.ResolverContextRef = in.ResolverContextRef + if in.AdditionalNames != nil { + in, out := &in.AdditionalNames, &out.AdditionalNames + *out = make([]DNSAdditionalNameRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.DNSZoneRef = in.DNSZoneRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSNamingPolicySpec. +func (in *DNSNamingPolicySpec) DeepCopy() *DNSNamingPolicySpec { + if in == nil { + return nil + } + out := new(DNSNamingPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSNamingPolicyStatus) DeepCopyInto(out *DNSNamingPolicyStatus) { + *out = *in + out.ResolvedResolverContextRef = in.ResolvedResolverContextRef + out.ResolvedDNSZoneRef = in.ResolvedDNSZoneRef + if in.ResolvedAdditionalNames != nil { + in, out := &in.ResolvedAdditionalNames, &out.ResolvedAdditionalNames + *out = make([]DNSResolvedAdditionalNameRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSNamingPolicyStatus. +func (in *DNSNamingPolicyStatus) DeepCopy() *DNSNamingPolicyStatus { + if in == nil { + return nil + } + out := new(DNSNamingPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSObjectReference) DeepCopyInto(out *DNSObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSObjectReference. +func (in *DNSObjectReference) DeepCopy() *DNSObjectReference { + if in == nil { + return nil + } + out := new(DNSObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRecordContribution) DeepCopyInto(out *DNSRecordContribution) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRecordContribution. +func (in *DNSRecordContribution) DeepCopy() *DNSRecordContribution { + if in == nil { + return nil + } + out := new(DNSRecordContribution) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSRecordContribution) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRecordContributionList) DeepCopyInto(out *DNSRecordContributionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSRecordContribution, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRecordContributionList. +func (in *DNSRecordContributionList) DeepCopy() *DNSRecordContributionList { + if in == nil { + return nil + } + out := new(DNSRecordContributionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSRecordContributionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRecordContributionSpec) DeepCopyInto(out *DNSRecordContributionSpec) { + *out = *in + out.RegistrationRef = in.RegistrationRef + if in.RecordSets != nil { + in, out := &in.RecordSets, &out.RecordSets + *out = make([]DNSContributionRecordSet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRecordContributionSpec. +func (in *DNSRecordContributionSpec) DeepCopy() *DNSRecordContributionSpec { + if in == nil { + return nil + } + out := new(DNSRecordContributionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRecordContributionStatus) DeepCopyInto(out *DNSRecordContributionStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRecordContributionStatus. +func (in *DNSRecordContributionStatus) DeepCopy() *DNSRecordContributionStatus { + if in == nil { + return nil + } + out := new(DNSRecordContributionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DNSRecordSet) DeepCopyInto(out *DNSRecordSet) { *out = *in @@ -199,32 +475,580 @@ func (in *DNSRecordSetStatus) DeepCopy() *DNSRecordSetStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DNSZone) DeepCopyInto(out *DNSZone) { +func (in *DNSRegistration) DeepCopyInto(out *DNSRegistration) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZone. -func (in *DNSZone) DeepCopy() *DNSZone { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistration. +func (in *DNSRegistration) DeepCopy() *DNSRegistration { if in == nil { return nil } - out := new(DNSZone) + out := new(DNSRegistration) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DNSZone) DeepCopyObject() runtime.Object { +func (in *DNSRegistration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRegistrationList) DeepCopyInto(out *DNSRegistrationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSRegistration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistrationList. +func (in *DNSRegistrationList) DeepCopy() *DNSRegistrationList { + if in == nil { + return nil + } + out := new(DNSRegistrationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSRegistrationList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRegistrationSelector) DeepCopyInto(out *DNSRegistrationSelector) { + *out = *in + if in.MatchLabels != nil { + in, out := &in.MatchLabels, &out.MatchLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]DNSRegistrationSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistrationSelector. +func (in *DNSRegistrationSelector) DeepCopy() *DNSRegistrationSelector { + if in == nil { + return nil + } + out := new(DNSRegistrationSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRegistrationSelectorRequirement) DeepCopyInto(out *DNSRegistrationSelectorRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistrationSelectorRequirement. +func (in *DNSRegistrationSelectorRequirement) DeepCopy() *DNSRegistrationSelectorRequirement { + if in == nil { + return nil + } + out := new(DNSRegistrationSelectorRequirement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRegistrationSpec) DeepCopyInto(out *DNSRegistrationSpec) { + *out = *in + out.DNSZoneRef = in.DNSZoneRef + if in.RecordTypes != nil { + in, out := &in.RecordTypes, &out.RecordTypes + *out = make([]RRType, len(*in)) + copy(*out, *in) + } + if in.ReservedDescendants != nil { + in, out := &in.ReservedDescendants, &out.ReservedDescendants + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistrationSpec. +func (in *DNSRegistrationSpec) DeepCopy() *DNSRegistrationSpec { + if in == nil { + return nil + } + out := new(DNSRegistrationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSRegistrationStatus) DeepCopyInto(out *DNSRegistrationStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSRegistrationStatus. +func (in *DNSRegistrationStatus) DeepCopy() *DNSRegistrationStatus { + if in == nil { + return nil + } + out := new(DNSRegistrationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolvedAdditionalNameRule) DeepCopyInto(out *DNSResolvedAdditionalNameRule) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + out.DNSZoneRef = in.DNSZoneRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolvedAdditionalNameRule. +func (in *DNSResolvedAdditionalNameRule) DeepCopy() *DNSResolvedAdditionalNameRule { + if in == nil { + return nil + } + out := new(DNSResolvedAdditionalNameRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverAccessBinding) DeepCopyInto(out *DNSResolverAccessBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverAccessBinding. +func (in *DNSResolverAccessBinding) DeepCopy() *DNSResolverAccessBinding { + if in == nil { + return nil + } + out := new(DNSResolverAccessBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSResolverAccessBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverAccessBindingList) DeepCopyInto(out *DNSResolverAccessBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSResolverAccessBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverAccessBindingList. +func (in *DNSResolverAccessBindingList) DeepCopy() *DNSResolverAccessBindingList { + if in == nil { + return nil + } + out := new(DNSResolverAccessBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSResolverAccessBindingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverAccessBindingSpec) DeepCopyInto(out *DNSResolverAccessBindingSpec) { + *out = *in + out.ContextRef = in.ContextRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverAccessBindingSpec. +func (in *DNSResolverAccessBindingSpec) DeepCopy() *DNSResolverAccessBindingSpec { + if in == nil { + return nil + } + out := new(DNSResolverAccessBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverAccessBindingStatus) DeepCopyInto(out *DNSResolverAccessBindingStatus) { + *out = *in + if in.BindingRef != nil { + in, out := &in.BindingRef, &out.BindingRef + *out = new(DNSObjectReference) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverAccessBindingStatus. +func (in *DNSResolverAccessBindingStatus) DeepCopy() *DNSResolverAccessBindingStatus { + if in == nil { + return nil + } + out := new(DNSResolverAccessBindingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverAccessVersion) DeepCopyInto(out *DNSResolverAccessVersion) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverAccessVersion. +func (in *DNSResolverAccessVersion) DeepCopy() *DNSResolverAccessVersion { + if in == nil { + return nil + } + out := new(DNSResolverAccessVersion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContext) DeepCopyInto(out *DNSResolverContext) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContext. +func (in *DNSResolverContext) DeepCopy() *DNSResolverContext { + if in == nil { + return nil + } + out := new(DNSResolverContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSResolverContext) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextList) DeepCopyInto(out *DNSResolverContextList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSResolverContext, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextList. +func (in *DNSResolverContextList) DeepCopy() *DNSResolverContextList { + if in == nil { + return nil + } + out := new(DNSResolverContextList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSResolverContextList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextManagedNamespace) DeepCopyInto(out *DNSResolverContextManagedNamespace) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextManagedNamespace. +func (in *DNSResolverContextManagedNamespace) DeepCopy() *DNSResolverContextManagedNamespace { + if in == nil { + return nil + } + out := new(DNSResolverContextManagedNamespace) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextManagedNamespaceStatus) DeepCopyInto(out *DNSResolverContextManagedNamespaceStatus) { + *out = *in + out.DNSZoneRef = in.DNSZoneRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextManagedNamespaceStatus. +func (in *DNSResolverContextManagedNamespaceStatus) DeepCopy() *DNSResolverContextManagedNamespaceStatus { + if in == nil { + return nil + } + out := new(DNSResolverContextManagedNamespaceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextServingTarget) DeepCopyInto(out *DNSResolverContextServingTarget) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextServingTarget. +func (in *DNSResolverContextServingTarget) DeepCopy() *DNSResolverContextServingTarget { + if in == nil { + return nil + } + out := new(DNSResolverContextServingTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextSpec) DeepCopyInto(out *DNSResolverContextSpec) { + *out = *in + out.ManagedNamespace = in.ManagedNamespace +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextSpec. +func (in *DNSResolverContextSpec) DeepCopy() *DNSResolverContextSpec { + if in == nil { + return nil + } + out := new(DNSResolverContextSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSResolverContextStatus) DeepCopyInto(out *DNSResolverContextStatus) { + *out = *in + out.ManagedNamespace = in.ManagedNamespace + if in.ServingTargets != nil { + in, out := &in.ServingTargets, &out.ServingTargets + *out = make([]DNSResolverContextServingTarget, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSResolverContextStatus. +func (in *DNSResolverContextStatus) DeepCopy() *DNSResolverContextStatus { + if in == nil { + return nil + } + out := new(DNSResolverContextStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSZone) DeepCopyInto(out *DNSZone) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZone. +func (in *DNSZone) DeepCopy() *DNSZone { + if in == nil { + return nil + } + out := new(DNSZone) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSZone) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSZoneAssociation) DeepCopyInto(out *DNSZoneAssociation) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZoneAssociation. +func (in *DNSZoneAssociation) DeepCopy() *DNSZoneAssociation { + if in == nil { + return nil + } + out := new(DNSZoneAssociation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSZoneAssociation) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSZoneAssociationList) DeepCopyInto(out *DNSZoneAssociationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DNSZoneAssociation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZoneAssociationList. +func (in *DNSZoneAssociationList) DeepCopy() *DNSZoneAssociationList { + if in == nil { + return nil + } + out := new(DNSZoneAssociationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DNSZoneAssociationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSZoneAssociationSpec) DeepCopyInto(out *DNSZoneAssociationSpec) { + *out = *in + out.DNSZoneRef = in.DNSZoneRef + out.ResolverContextRef = in.ResolverContextRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZoneAssociationSpec. +func (in *DNSZoneAssociationSpec) DeepCopy() *DNSZoneAssociationSpec { + if in == nil { + return nil + } + out := new(DNSZoneAssociationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DNSZoneAssociationStatus) DeepCopyInto(out *DNSZoneAssociationStatus) { + *out = *in + out.ResolvedDNSZoneRef = in.ResolvedDNSZoneRef + out.ResolvedResolverContextRef = in.ResolvedResolverContextRef + if in.BindingRef != nil { + in, out := &in.BindingRef, &out.BindingRef + *out = new(DNSObjectReference) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSZoneAssociationStatus. +func (in *DNSZoneAssociationStatus) DeepCopy() *DNSZoneAssociationStatus { + if in == nil { + return nil + } + out := new(DNSZoneAssociationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DNSZoneClass) DeepCopyInto(out *DNSZoneClass) { *out = *in diff --git a/config/crd/bases/dns.networking.miloapis.com_dnsnamingpolicies.yaml b/config/crd/bases/dns.networking.miloapis.com_dnsnamingpolicies.yaml new file mode 100644 index 00000000..e835d310 --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnsnamingpolicies.yaml @@ -0,0 +1,376 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: dnsnamingpolicies.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSNamingPolicy + listKind: DNSNamingPolicyList + plural: dnsnamingpolicies + singular: dnsnamingpolicy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + additionalNames: + items: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + namePrefix: + type: string + selector: + description: DNSRegistrationSelector matches registration labels + using Kubernetes selector semantics. + properties: + matchExpressions: + items: + properties: + key: + maxLength: 317 + minLength: 1 + type: string + operator: + description: A label selector operator is the set + of operators that can be used in a selector requirement. + enum: + - In + - NotIn + - Exists + - DoesNotExist + type: string + values: + items: + maxLength: 63 + pattern: ^([A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$ + type: string + maxItems: 64 + type: array + required: + - key + - operator + type: object + x-kubernetes-validations: + - message: selector operator requires compatible values + rule: '(self.operator in [''In'', ''NotIn'']) ? (has(self.values) + && self.values.size() > 0) : (!has(self.values) || + self.values.size() == 0)' + maxItems: 64 + type: array + matchLabels: + additionalProperties: + type: string + maxProperties: 64 + type: object + type: object + required: + - dnsZoneRef + - namePrefix + type: object + x-kubernetes-validations: + - message: dnsZoneRef must pin the zone UID + rule: has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > + 0 + type: array + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + priority: + format: int32 + type: integer + resolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + scopePrefix: + type: string + required: + - additionalNames + - resolverContextRef + type: object + x-kubernetes-validations: + - message: a nonempty resolverContextRef must pin name and UID + rule: '!has(self.resolverContextRef) || ((self.resolverContextRef.name.size() + == 0 && !has(self.resolverContextRef.uid) && (!has(self.resolverContextRef.generation) + || self.resolverContextRef.generation == 0)) || (self.resolverContextRef.name.size() + > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() + > 0))' + - message: a selected dnsZoneRef must pin the zone UID + rule: '!has(self.dnsZoneRef) || self.dnsZoneRef.name.size() == 0 || + (has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0)' + - message: resolverContextRef is required + rule: self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) + && self.resolverContextRef.uid.size() > 0 + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + resolvedAdditionalNames: + items: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + namePrefix: + type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - dnsZoneRef + - namePrefix + type: object + type: array + resolvedDNSZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedResolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dns.networking.miloapis.com_dnsrecordcontributions.yaml b/config/crd/bases/dns.networking.miloapis.com_dnsrecordcontributions.yaml new file mode 100644 index 00000000..18e6441c --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnsrecordcontributions.yaml @@ -0,0 +1,394 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsrecordcontributions.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSRecordContribution + listKind: DNSRecordContributionList + plural: dnsrecordcontributions + singular: dnsrecordcontribution + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + eligible: + type: boolean + reason: + type: string + recordSets: + items: + properties: + recordType: + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + records: + items: + description: RecordEntry represents one owner name and its + values. + properties: + a: + description: Exactly one of the following type-specific + fields should be set matching RecordType. + properties: + content: + format: ipv4 + type: string + required: + - content + type: object + aaaa: + properties: + content: + format: ipv6 + type: string + required: + - content + type: object + alias: + description: |- + ALIASRecordSpec is like CNAME but intended for providers that implement an ALIAS/ANAME-style record. + The content is a hostname (FQDN or relative) with an optional trailing dot. + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + caa: + properties: + flag: + description: 0–255 flag + maximum: 255 + minimum: 0 + type: integer + tag: + description: 'RFC-style tags: keep it simple: [a-z0-9]+' + minLength: 1 + pattern: ^[a-z0-9]+$ + type: string + value: + minLength: 1 + type: string + required: + - flag + - tag + - value + type: object + cname: + properties: + content: + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?)(?:\.([A-Za-z0-9_](?:[-A-Za-z0-9_]{0,61}[A-Za-z0-9_])?))*\.?$ + type: string + required: + - content + type: object + https: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + mx: + properties: + exchange: + minLength: 1 + type: string + preference: + maximum: 65535 + minimum: 0 + type: integer + required: + - exchange + - preference + type: object + name: + description: Name is the owner name (relative to the zone + or FQDN). + minLength: 1 + pattern: ^(@|[A-Za-z0-9*._-]+)$ + type: string + ns: + properties: + content: + description: |- + Require a hostname (FQDN or relative), allow optional trailing dot, no underscores. + Labels: 1-63 chars, alphanum with interior hyphens, total length <=253. + maxLength: 253 + minLength: 1 + pattern: ^([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*\.?$ + type: string + required: + - content + type: object + ptr: + properties: + content: + type: string + required: + - content + type: object + soa: + properties: + expire: + format: int32 + type: integer + mname: + minLength: 1 + type: string + refresh: + format: int32 + type: integer + retry: + format: int32 + type: integer + rname: + minLength: 1 + type: string + serial: + format: int32 + type: integer + ttl: + format: int32 + type: integer + required: + - mname + - rname + type: object + srv: + properties: + port: + maximum: 65535 + minimum: 0 + type: integer + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + minLength: 1 + type: string + weight: + maximum: 65535 + minimum: 0 + type: integer + required: + - port + - priority + - target + - weight + type: object + svcb: + properties: + params: + additionalProperties: + type: string + type: object + priority: + maximum: 65535 + minimum: 0 + type: integer + target: + type: string + required: + - priority + - target + type: object + tlsa: + properties: + certData: + type: string + matchingType: + type: integer + selector: + type: integer + usage: + type: integer + required: + - certData + - matchingType + - selector + - usage + type: object + ttl: + description: TTL optionally overrides TTL for this owner/RRset. + format: int64 + type: integer + txt: + properties: + content: + type: string + required: + - content + type: object + required: + - name + type: object + type: array + required: + - recordType + - records + type: object + type: array + registrationRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - eligible + - recordSets + - registrationRef + type: object + x-kubernetes-validations: + - message: registrationRef requires name, UID, and generation + rule: self.registrationRef.name.size() > 0 && has(self.registrationRef.uid) + && self.registrationRef.uid.size() > 0 && has(self.registrationRef.generation) + && self.registrationRef.generation > 0 + - message: contribution registration identity is immutable + rule: self.registrationRef.name == oldSelf.registrationRef.name && self.registrationRef.uid + == oldSelf.registrationRef.uid + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + publishedRevision: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dns.networking.miloapis.com_dnsregistrations.yaml b/config/crd/bases/dns.networking.miloapis.com_dnsregistrations.yaml new file mode 100644 index 00000000..2eca271a --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnsregistrations.yaml @@ -0,0 +1,180 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsregistrations.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSRegistration + listKind: DNSRegistrationList + plural: dnsregistrations + singular: dnsregistration + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + name: + type: string + recordTypes: + items: + enum: + - A + - AAAA + - ALIAS + - CNAME + - TXT + - MX + - SRV + - CAA + - NS + - SOA + - PTR + - TLSA + - HTTPS + - SVCB + type: string + type: array + reservedDescendants: + items: + type: string + type: array + ttlSeconds: + description: TTLSeconds bounds cached dynamic answers to at most 30 + seconds after withdrawal. + format: int32 + maximum: 30 + minimum: 1 + type: integer + required: + - dnsZoneRef + - name + - recordTypes + - ttlSeconds + type: object + x-kubernetes-validations: + - message: dnsZoneRef must pin the zone UID + rule: has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0 + status: + properties: + canonicalFQDN: + type: string + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + publicationRevision: + format: int64 + type: integer + publicationWriterEpoch: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dns.networking.miloapis.com_dnsresolveraccessbindings.yaml b/config/crd/bases/dns.networking.miloapis.com_dnsresolveraccessbindings.yaml new file mode 100644 index 00000000..f7ae257d --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnsresolveraccessbindings.yaml @@ -0,0 +1,177 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsresolveraccessbindings.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSResolverAccessBinding + listKind: DNSResolverAccessBindingList + plural: dnsresolveraccessbindings + singular: dnsresolveraccessbinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + contextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + destinationAddress: + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: destinationAddress must be an IP address + rule: isIP(self) + region: + maxLength: 253 + minLength: 1 + type: string + required: + - contextRef + - destinationAddress + - region + type: object + x-kubernetes-validations: + - message: contextRef requires name and UID + rule: self.contextRef.name.size() > 0 && has(self.contextRef.uid) && + self.contextRef.uid.size() > 0 + - message: contextRef is immutable + rule: self.contextRef == oldSelf.contextRef + - message: access destination is immutable + rule: self.region == oldSelf.region && self.destinationAddress == oldSelf.destinationAddress + status: + properties: + bindingRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dns.networking.miloapis.com_dnsresolvercontexts.yaml b/config/crd/bases/dns.networking.miloapis.com_dnsresolvercontexts.yaml new file mode 100644 index 00000000..64cc3207 --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnsresolvercontexts.yaml @@ -0,0 +1,163 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnsresolvercontexts.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSResolverContext + listKind: DNSResolverContextList + plural: dnsresolvercontexts + singular: dnsresolvercontext + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + DNSResolverContext is the DNS-owned serving and namespace boundary created by + a trusted network integration. consumerID is opaque to DNS and immutable. + properties: + consumerID: + maxLength: 512 + minLength: 1 + type: string + managedNamespace: + properties: + enabled: + type: boolean + type: object + required: + - consumerID + type: object + x-kubernetes-validations: + - message: consumerID is immutable + rule: oldSelf.consumerID == self.consumerID + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + managedNamespace: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + suffix: + type: string + type: object + servingTargets: + items: + properties: + region: + type: string + shard: + type: string + required: + - region + - shard + type: object + type: array + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/dns.networking.miloapis.com_dnszoneassociations.yaml b/config/crd/bases/dns.networking.miloapis.com_dnszoneassociations.yaml new file mode 100644 index 00000000..36b832a2 --- /dev/null +++ b/config/crd/bases/dns.networking.miloapis.com_dnszoneassociations.yaml @@ -0,0 +1,234 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + discovery.miloapis.com/parent-contexts: Project + name: dnszoneassociations.dns.networking.miloapis.com +spec: + group: dns.networking.miloapis.com + names: + kind: DNSZoneAssociation + listKind: DNSZoneAssociationList + plural: dnszoneassociations + singular: dnszoneassociation + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + dnsZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + managed: + type: boolean + resolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + required: + - dnsZoneRef + - resolverContextRef + type: object + x-kubernetes-validations: + - message: a nonempty resolverContextRef must pin name and UID + rule: '!has(self.resolverContextRef) || ((self.resolverContextRef.name.size() + == 0 && !has(self.resolverContextRef.uid) && (!has(self.resolverContextRef.generation) + || self.resolverContextRef.generation == 0)) || (self.resolverContextRef.name.size() + > 0 && has(self.resolverContextRef.uid) && self.resolverContextRef.uid.size() + > 0))' + - message: dnsZoneRef must pin the zone UID + rule: has(self.dnsZoneRef.uid) && self.dnsZoneRef.uid.size() > 0 + - message: resolverContextRef is required + rule: self.resolverContextRef.name.size() > 0 && has(self.resolverContextRef.uid) + && self.resolverContextRef.uid.size() > 0 + status: + properties: + bindingRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + resolvedDNSZoneRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + resolvedResolverContextRef: + description: |- + DNSObjectReference pins a namespaced object by API-assigned UID. Generation + is used where a writer must also be fenced against policy changes. + properties: + generation: + format: int64 + minimum: 0 + type: integer + name: + maxLength: 253 + type: string + uid: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + maxLength: 128 + type: string + required: + - name + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 8fc3e9a2..81e6435a 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,11 +6,19 @@ resources: - bases/dns.networking.miloapis.com_dnszones.yaml - bases/dns.networking.miloapis.com_dnsrecordsets.yaml - bases/dns.networking.miloapis.com_dnszonediscoveries.yaml + + +- bases/dns.networking.miloapis.com_dnsnamingpolicies.yaml +- bases/dns.networking.miloapis.com_dnsrecordcontributions.yaml +- bases/dns.networking.miloapis.com_dnsregistrations.yaml +- bases/dns.networking.miloapis.com_dnsresolveraccessbindings.yaml +- bases/dns.networking.miloapis.com_dnsresolvercontexts.yaml +- bases/dns.networking.miloapis.com_dnszoneassociations.yaml # +kubebuilder:scaffold:crdkustomizeresource -patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD +#patches: # +kubebuilder:scaffold:crdkustomizewebhookpatch # [WEBHOOK] To enable webhook, uncomment the following section diff --git a/config/iam/protected-resources/internal-dns.yaml b/config/iam/protected-resources/internal-dns.yaml new file mode 100644 index 00000000..d39df207 --- /dev/null +++ b/config/iam/protected-resources/internal-dns.yaml @@ -0,0 +1,107 @@ +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnszoneassociation +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSZoneAssociation + plural: dnszoneassociations + singular: dnszoneassociation + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project +--- +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnsnamingpolicy +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSNamingPolicy + plural: dnsnamingpolicies + singular: dnsnamingpolicy + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project +--- +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnsregistration +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSRegistration + plural: dnsregistrations + singular: dnsregistration + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project +--- +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnsrecordcontribution +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSRecordContribution + plural: dnsrecordcontributions + singular: dnsrecordcontribution + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project +--- +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnsresolvercontext +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSResolverContext + plural: dnsresolvercontexts + singular: dnsresolvercontext + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project +--- +apiVersion: iam.miloapis.com/v1alpha1 +kind: ProtectedResource +metadata: + name: dns.networking.miloapis.com-dnsresolveraccessbinding +spec: + serviceRef: + name: dns.networking.miloapis.com + kind: DNSResolverAccessBinding + plural: dnsresolveraccessbindings + singular: dnsresolveraccessbinding + permissions: [list, get, watch, create, update, patch, delete] + subresources: + - name: status + permissions: [get, update, patch] + parentResources: + - apiGroup: resourcemanager.miloapis.com + kind: Project diff --git a/config/iam/protected-resources/kustomization.yaml b/config/iam/protected-resources/kustomization.yaml index 2df1df9c..c5b52617 100644 --- a/config/iam/protected-resources/kustomization.yaml +++ b/config/iam/protected-resources/kustomization.yaml @@ -10,3 +10,4 @@ resources: - dnsrecordsets.yaml - dnszoneclasses.yaml - dnszonediscoveries.yaml + - internal-dns.yaml diff --git a/config/iam/roles/dns-admin.yaml b/config/iam/roles/dns-admin.yaml index a01343f2..a5a01d5c 100644 --- a/config/iam/roles/dns-admin.yaml +++ b/config/iam/roles/dns-admin.yaml @@ -22,5 +22,15 @@ spec: - dns.networking.miloapis.com/dnszonediscoveries.update - dns.networking.miloapis.com/dnszonediscoveries.patch - dns.networking.miloapis.com/dnszonediscoveries.delete - - + - dns.networking.miloapis.com/dnszoneassociations.create + - dns.networking.miloapis.com/dnszoneassociations.update + - dns.networking.miloapis.com/dnszoneassociations.patch + - dns.networking.miloapis.com/dnszoneassociations.delete + - dns.networking.miloapis.com/dnsnamingpolicies.create + - dns.networking.miloapis.com/dnsnamingpolicies.update + - dns.networking.miloapis.com/dnsnamingpolicies.patch + - dns.networking.miloapis.com/dnsnamingpolicies.delete + - dns.networking.miloapis.com/dnsregistrations.create + - dns.networking.miloapis.com/dnsregistrations.update + - dns.networking.miloapis.com/dnsregistrations.patch + - dns.networking.miloapis.com/dnsregistrations.delete diff --git a/config/iam/roles/dns-viewer.yaml b/config/iam/roles/dns-viewer.yaml index 3bc1124c..84c6a486 100644 --- a/config/iam/roles/dns-viewer.yaml +++ b/config/iam/roles/dns-viewer.yaml @@ -24,5 +24,21 @@ spec: - dns.networking.miloapis.com/dnszonediscoveries.get - dns.networking.miloapis.com/dnszonediscoveries/status.get - dns.networking.miloapis.com/dnszonediscoveries.watch - - + - dns.networking.miloapis.com/dnszoneassociations.list + - dns.networking.miloapis.com/dnszoneassociations.get + - dns.networking.miloapis.com/dnszoneassociations.watch + - dns.networking.miloapis.com/dnsnamingpolicies.list + - dns.networking.miloapis.com/dnsnamingpolicies.get + - dns.networking.miloapis.com/dnsnamingpolicies.watch + - dns.networking.miloapis.com/dnsregistrations.list + - dns.networking.miloapis.com/dnsregistrations.get + - dns.networking.miloapis.com/dnsregistrations.watch + - dns.networking.miloapis.com/dnsrecordcontributions.list + - dns.networking.miloapis.com/dnsrecordcontributions.get + - dns.networking.miloapis.com/dnsrecordcontributions.watch + - dns.networking.miloapis.com/dnsresolvercontexts.list + - dns.networking.miloapis.com/dnsresolvercontexts.get + - dns.networking.miloapis.com/dnsresolvercontexts.watch + - dns.networking.miloapis.com/dnsresolveraccessbindings.list + - dns.networking.miloapis.com/dnsresolveraccessbindings.get + - dns.networking.miloapis.com/dnsresolveraccessbindings.watch diff --git a/config/internal-dns/examples/context.yaml b/config/internal-dns/examples/context.yaml new file mode 100644 index 00000000..337d38c2 --- /dev/null +++ b/config/internal-dns/examples/context.yaml @@ -0,0 +1,11 @@ +apiVersion: dns.networking.miloapis.com/v1alpha1 +kind: DNSResolverContext +metadata: + name: application-network + namespace: project +spec: + # The network integration supplies an opaque, stable consumer identity. + consumerID: network-integration/application-network + # Products discover this namespace instead of asking users to select a zone. + managedNamespace: + enabled: true diff --git a/internal/internaldns/admission/handler.go b/internal/internaldns/admission/handler.go new file mode 100644 index 00000000..6611beba --- /dev/null +++ b/internal/internaldns/admission/handler.go @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Package admission validates live references between private DNS resources. +package admission + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + dnsv1alpha1 "go.miloapis.com/dns-operator/api/v1alpha1" + "go.miloapis.com/dns-operator/internal/internaldns/model" + admissionv1 "k8s.io/api/admission/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + kubeadmission "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +const subresourceStatus = "status" + +const contributionResource = "dnsrecordcontributions" + +const Path = "/validate-internal-dns" + +// Handler uses an uncached client selected by authenticated project context. +type Handler struct { + Client client.Client + + ClientForRequest func(context.Context, kubeadmission.Request) (client.Client, error) +} + +var _ kubeadmission.Handler = (*Handler)(nil) + +func (h *Handler) Handle(ctx context.Context, req kubeadmission.Request) kubeadmission.Response { + if req.Resource.Group != dnsv1alpha1.GroupVersion.Group { + return kubeadmission.Denied("unexpected API group") + } + // The API server authorizes resource and status access through RBAC. + if req.Operation == admissionv1.Delete { + return kubeadmission.Allowed("API server authorization applies") + } + if req.SubResource == subresourceStatus { + return kubeadmission.Allowed("API server status authorization applies") + } + switch req.Resource.Resource { + case "dnsregistrations", contributionResource, "dnszoneassociations", "dnsnamingpolicies", "dnsresolveraccessbindings": + // These resources require live references. + default: + return kubeadmission.Allowed("API server authorization and schema validation apply") + } + if req.Operation == admissionv1.Update { + var object metav1.PartialObjectMetadata + if err := json.Unmarshal(req.Object.Raw, &object); err != nil { + return kubeadmission.Denied("invalid resource metadata") + } + if !object.DeletionTimestamp.IsZero() { + return kubeadmission.Allowed("resource finalization does not require live references") + } + } + cl := h.Client + if h.ClientForRequest != nil { + var err error + cl, err = h.ClientForRequest(ctx, req) + if err != nil { + return kubeadmission.Errored(http.StatusServiceUnavailable, err) + } + } + if cl == nil { + return kubeadmission.Errored(http.StatusServiceUnavailable, fmt.Errorf("project API client unavailable")) + } + var err error + switch req.Resource.Resource { + case "dnsresolveraccessbindings": + err = h.resolverAccess(ctx, cl, req) + case "dnsregistrations": + err = h.registration(ctx, cl, req) + case contributionResource: + err = h.contribution(ctx, cl, req) + case "dnszoneassociations": + err = h.association(ctx, cl, req) + case "dnsnamingpolicies": + err = h.namingPolicy(ctx, cl, req) + default: + return kubeadmission.Allowed("project API authorization applies") + } + if err != nil { + return kubeadmission.Denied(err.Error()) + } + return kubeadmission.Allowed("DNS references are valid") +} + +func (h *Handler) resolverAccess(ctx context.Context, cl client.Client, req kubeadmission.Request) error { + var current dnsv1alpha1.DNSResolverAccessBinding + if err := decode(req, ¤t); err != nil { + return err + } + var resolverContext dnsv1alpha1.DNSResolverContext + if err := cl.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: current.Spec.ContextRef.Name}, &resolverContext); err != nil { + return fmt.Errorf("resolver context lookup: %w", err) + } + if resolverContext.UID != current.Spec.ContextRef.UID || !resolverContext.DeletionTimestamp.IsZero() { + return fmt.Errorf("resolver context lifetime is not current") + } + ready := apimeta.FindStatusCondition(resolverContext.Status.Conditions, "Ready") + if ready == nil || ready.Status != metav1.ConditionTrue || ready.ObservedGeneration != resolverContext.Generation { + return fmt.Errorf("resolver context is not ready for its current generation") + } + return nil +} + +func (h *Handler) contribution(ctx context.Context, cl client.Client, req kubeadmission.Request) error { + var contribution dnsv1alpha1.DNSRecordContribution + if err := decode(req, &contribution); err != nil { + return err + } + var registration dnsv1alpha1.DNSRegistration + if err := cl.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: contribution.Spec.RegistrationRef.Name}, ®istration); err != nil { + return err + } + if registration.UID != contribution.Spec.RegistrationRef.UID || registration.Generation != contribution.Spec.RegistrationRef.Generation || !registration.DeletionTimestamp.IsZero() { + return fmt.Errorf("registration lifetime or generation is not current") + } + zone, err := currentPrivateZone(ctx, cl, req.Namespace, registration.Spec.DNSZoneRef) + if err != nil { + return err + } + for _, set := range contribution.Spec.RecordSets { + if !containsType(registration.Spec.RecordTypes, set.RecordType) { + return fmt.Errorf("record type is outside the registration") + } + for _, record := range set.Records { + if err := model.ValidatePrivateRecordOwner(record.Name, zone.Spec.DomainName, string(set.RecordType)); err != nil { + return err + } + } + } + return nil +} + +func (h *Handler) association(ctx context.Context, cl client.Client, req kubeadmission.Request) error { + var a dnsv1alpha1.DNSZoneAssociation + if err := decode(req, &a); err != nil { + return err + } + if _, err := currentPrivateZone(ctx, cl, req.Namespace, a.Spec.DNSZoneRef); err != nil { + return err + } + + if a.Spec.ResolverContextRef.UID == "" { + return fmt.Errorf("association must pin resolver context UID") + } + return nil +} + +func (h *Handler) namingPolicy(ctx context.Context, cl client.Client, req kubeadmission.Request) error { + var p dnsv1alpha1.DNSNamingPolicy + if err := decode(req, &p); err != nil { + return err + } + + if p.Spec.DNSZoneRef.Name != "" { + if _, err := currentPrivateZone(ctx, cl, req.Namespace, p.Spec.DNSZoneRef); err != nil { + return err + } + } + for _, rule := range p.Spec.AdditionalNames { + if _, err := metav1.LabelSelectorAsSelector(rule.Selector.AsLabelSelector()); err != nil { + return fmt.Errorf("invalid naming selector: %w", err) + } + if _, err := currentPrivateZone(ctx, cl, req.Namespace, rule.DNSZoneRef); err != nil { + return err + } + } + + if p.Spec.ResolverContextRef.Name == "" || p.Spec.ResolverContextRef.UID == "" { + return fmt.Errorf("naming policy must pin resolver context name and UID") + } + var resolverContext dnsv1alpha1.DNSResolverContext + if err := cl.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: p.Spec.ResolverContextRef.Name}, &resolverContext); err != nil { + return fmt.Errorf("resolver context lookup: %w", err) + } + ready := apimeta.FindStatusCondition(resolverContext.Status.Conditions, "Ready") + if resolverContext.UID != p.Spec.ResolverContextRef.UID || !resolverContext.DeletionTimestamp.IsZero() || ready == nil || ready.Status != metav1.ConditionTrue || ready.ObservedGeneration != resolverContext.Generation { + return fmt.Errorf("resolver context lifetime is not ready") + } + return nil +} + +func decode(req kubeadmission.Request, obj any) error { + data := req.Object.Raw + if req.Operation == admissionv1.Delete { + data = req.OldObject.Raw + } + if len(data) == 0 { + return fmt.Errorf("admission object is missing") + } + return json.Unmarshal(data, obj) +} + +func containsType(values []dnsv1alpha1.RRType, value dnsv1alpha1.RRType) bool { + for _, v := range values { + if v == value { + return true + } + } + return false +} + +func (h *Handler) registration(ctx context.Context, cl client.Client, req kubeadmission.Request) error { + var registration dnsv1alpha1.DNSRegistration + if err := decode(req, ®istration); err != nil { + return err + } + + zone, err := currentPrivateZone(ctx, cl, req.Namespace, registration.Spec.DNSZoneRef) + if err != nil { + return err + } + for _, rt := range registration.Spec.RecordTypes { + if err := model.ValidatePrivateRecordOwner(registration.Spec.Name, zone.Spec.DomainName, string(rt)); err != nil { + return err + } + } + return nil +} + +// Zone references cannot silently follow a replacement with the same name. +func currentPrivateZone(ctx context.Context, cl client.Client, namespace string, ref dnsv1alpha1.DNSObjectReference) (*dnsv1alpha1.DNSZone, error) { + if ref.Name == "" || ref.UID == "" { + return nil, fmt.Errorf("zone reference must pin name and UID") + } + var zone dnsv1alpha1.DNSZone + if err := cl.Get(ctx, client.ObjectKey{Namespace: namespace, Name: ref.Name}, &zone); err != nil { + return nil, fmt.Errorf("private zone lookup: %w", err) + } + if zone.UID != ref.UID || !zone.DeletionTimestamp.IsZero() || (ref.Generation != 0 && ref.Generation != zone.Generation) { + return nil, fmt.Errorf("zone lifetime is not current") + } + if zone.Spec.Visibility != dnsv1alpha1.DNSZoneVisibilityPrivate { + return nil, fmt.Errorf("internal references require a private zone") + } + return &zone, nil +} diff --git a/internal/internaldns/admission/handler_test.go b/internal/internaldns/admission/handler_test.go new file mode 100644 index 00000000..e0151027 --- /dev/null +++ b/internal/internaldns/admission/handler_test.go @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package admission + +import ( + "context" + "encoding/json" + "testing" + "time" + + dnsv1alpha1 "go.miloapis.com/dns-operator/api/v1alpha1" + admissionv1 "k8s.io/api/admission/v1" + authenticationv1 "k8s.io/api/authentication/v1" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + kubeadmission "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +func TestResourceAccessUsesAPIServerAuthorization(t *testing.T) { + h := Handler{} + for _, resource := range []string{"dnsresolvercontexts", "dnsresolverbindings", "dnspublicationmanifests", "dnspublicationchunks", "dnstransportoutboxes", "dnspublicationownerships"} { + if !h.Handle(context.Background(), request(resource, "authorized-user", map[string]any{}, nil)).Allowed { + t.Fatalf("admission duplicates reaccess version for %s", resource) + } + } + req := request("dnsrecordcontributions", "authorized-user", map[string]any{}, nil) + req.SubResource = "status" + if !h.Handle(context.Background(), req).Allowed { + t.Fatal("admission duplicates status authorization") + } +} + +func TestUnavailableProjectContextFailsClosed(t *testing.T) { + h := Handler{} + res := h.Handle(context.Background(), request("dnsrecordcontributions", "publisher", map[string]any{}, nil)) + if res.Allowed || res.Result == nil || res.Result.Code != 503 { + t.Fatalf("must fail closed: %#v", res) + } +} + +func request(resource, username string, obj, old any) kubeadmission.Request { + raw, _ := json.Marshal(obj) + var oldRaw []byte + if old != nil { + oldRaw, _ = json.Marshal(old) + } + return kubeadmission.Request{AdmissionRequest: admissionv1.AdmissionRequest{ + UID: types.UID("request-uid"), Namespace: "project", Operation: admissionv1.Update, + Resource: metav1.GroupVersionResource{Group: dnsv1alpha1.GroupVersion.Group, Version: "v1alpha1", Resource: resource}, + Kind: metav1.GroupVersionKind{Group: dnsv1alpha1.GroupVersion.Group, Version: "v1alpha1"}, + UserInfo: authenticationv1.UserInfo{Username: username}, Object: runtime.RawExtension{Raw: raw}, OldObject: runtime.RawExtension{Raw: oldRaw}, + }} +} + +func TestInternalAdmissionPreservesPublicZoneControllerStatus(t *testing.T) { + h := Handler{} + req := kubeadmission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Update, Resource: metav1.GroupVersionResource{Group: dnsv1alpha1.GroupVersion.Group, Version: "v1alpha1", Resource: "dnszones"}, SubResource: "status", UserInfo: authenticationv1.UserInfo{Username: "system:serviceaccount:dns-system:public-controller"}}} + zone := dnsv1alpha1.DNSZone{Spec: dnsv1alpha1.DNSZoneSpec{Visibility: dnsv1alpha1.DNSZoneVisibilityPublic}} + req.Object.Raw, _ = json.Marshal(zone) + if !h.Handle(context.Background(), req).Allowed { + t.Fatal("internal admission blocked legacy public-zone status") + } + zone.Spec.Visibility = dnsv1alpha1.DNSZoneVisibilityPrivate + req.Object.Raw, _ = json.Marshal(zone) + if !h.Handle(context.Background(), req).Allowed { + t.Fatal("status authorization must use the API server") + } +} + +func TestProductIntentCanBeDeletedWithoutLiveReferences(t *testing.T) { + h := Handler{} + req := kubeadmission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Delete, Resource: metav1.GroupVersionResource{Group: dnsv1alpha1.GroupVersion.Group, Version: "v1alpha1", Resource: "dnsrecordcontributions"}, UserInfo: authenticationv1.UserInfo{Username: "system:serviceaccount:compute:publisher"}}} + if !h.Handle(context.Background(), req).Allowed { + t.Fatal("contribution cleanup required live references") + } + req.Resource.Resource = "dnspublicationownerships" + if !h.Handle(context.Background(), req).Allowed { + t.Fatal("deletion authorization must use the API server") + } +} + +func TestResolverAccessRequiresCurrentContext(t *testing.T) { + now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) + scheme := runtime.NewScheme() + if err := dnsv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + resolverContext := &dnsv1alpha1.DNSResolverContext{ObjectMeta: metav1.ObjectMeta{Name: "ctx", Namespace: "project", UID: "ctx-uid", Generation: 1}, Status: dnsv1alpha1.DNSResolverContextStatus{}} + setAdmissionCondition(&resolverContext.Status.Conditions, "Ready", metav1.ConditionTrue, 1, now) + h := Handler{Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(resolverContext).Build()} + old := &dnsv1alpha1.DNSResolverAccessBinding{Spec: dnsv1alpha1.DNSResolverAccessBindingSpec{ContextRef: dnsv1alpha1.DNSObjectReference{Name: "ctx", UID: "ctx-uid"}, Region: "central", DestinationAddress: "fd70:100::10"}} + current := old.DeepCopy() + req := request("dnsresolveraccessbindings", "system:serviceaccount:network-system:integration", current, old) + if res := h.Handle(context.Background(), req); !res.Allowed { + t.Fatalf("current integration update denied: %v", res.Result) + } + forged := current.DeepCopy() + forged.Spec.ContextRef.UID = "replacement" + req = request("dnsresolveraccessbindings", "system:serviceaccount:network-system:integration", forged, old) + if h.Handle(context.Background(), req).Allowed { + t.Fatal("context lifetime changed in place") + } + staleContext := resolverContext.DeepCopy() + staleContext.Generation = 2 + h.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(staleContext).Build() + req = request("dnsresolveraccessbindings", "system:serviceaccount:network-system:integration", current, old) + if h.Handle(context.Background(), req).Allowed { + t.Fatal("stale context Ready condition authorized access") + } +} + +func setAdmissionCondition(conditions *[]metav1.Condition, conditionType string, status metav1.ConditionStatus, generation int64, now time.Time) { + apimeta.SetStatusCondition(conditions, metav1.Condition{Type: conditionType, Status: status, Reason: "Test", ObservedGeneration: generation, LastTransitionTime: metav1.NewTime(now)}) +} + +func TestPrivateRegistrationRejectsAuthorityOverrides(t *testing.T) { + scheme := runtime.NewScheme() + if err := dnsv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + zone := &dnsv1alpha1.DNSZone{ObjectMeta: metav1.ObjectMeta{Name: "private", Namespace: "project", UID: "zone-uid"}, Spec: dnsv1alpha1.DNSZoneSpec{DomainName: "corp.internal", Visibility: dnsv1alpha1.DNSZoneVisibilityPrivate}} + h := Handler{Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(zone).Build()} + for _, tt := range []struct { + owner string + rt dnsv1alpha1.RRType + allowed bool + }{{"api", dnsv1alpha1.RRTypeA, true}, {"api", dnsv1alpha1.RRTypeCNAME, true}, {"@", dnsv1alpha1.RRTypeCNAME, false}, {"CORP.INTERNAL.", dnsv1alpha1.RRTypeCNAME, false}, {"@", dnsv1alpha1.RRTypeSOA, false}} { + registration := dnsv1alpha1.DNSRegistration{Spec: dnsv1alpha1.DNSRegistrationSpec{DNSZoneRef: dnsv1alpha1.DNSObjectReference{Name: zone.Name, UID: zone.UID}, TTLSeconds: 5, Name: tt.owner, RecordTypes: []dnsv1alpha1.RRType{tt.rt}}} + response := h.Handle(context.Background(), request("dnsregistrations", "tenant", registration, nil)) + if response.Allowed != tt.allowed { + t.Fatalf("owner %s type %s allowed=%v expected=%v", tt.owner, tt.rt, response.Allowed, tt.allowed) + } + } +} + +func TestRegistrationRequiresCurrentZone(t *testing.T) { + scheme := runtime.NewScheme() + if err := dnsv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + zone := &dnsv1alpha1.DNSZone{ObjectMeta: metav1.ObjectMeta{Name: "private", Namespace: "project", UID: "current"}, Spec: dnsv1alpha1.DNSZoneSpec{DomainName: "corp.internal", Visibility: dnsv1alpha1.DNSZoneVisibilityPrivate}} + h := Handler{Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(zone).Build()} + for _, tt := range []struct { + uid types.UID + ttl int32 + allowed bool + }{ + {"current", 1, true}, {"current", 30, true}, {"", 5, false}, {"retired", 5, false}, + } { + reg := dnsv1alpha1.DNSRegistration{Spec: dnsv1alpha1.DNSRegistrationSpec{DNSZoneRef: dnsv1alpha1.DNSObjectReference{Name: zone.Name, UID: tt.uid}, TTLSeconds: tt.ttl, Name: "api", RecordTypes: []dnsv1alpha1.RRType{dnsv1alpha1.RRTypeA}}} + if got := h.Handle(context.Background(), request("dnsregistrations", "tenant", reg, nil)); got.Allowed != tt.allowed { + t.Errorf("UID=%q TTL=%d: allowed=%v: %v", tt.uid, tt.ttl, got.Allowed, got.Result) + } + } +} + +func TestContributionValidatesReferencesForRBACAuthorizedCallers(t *testing.T) { + scheme := runtime.NewScheme() + if err := dnsv1alpha1.AddToScheme(scheme); err != nil { + t.Fatal(err) + } + zone := &dnsv1alpha1.DNSZone{ObjectMeta: metav1.ObjectMeta{Name: "private", Namespace: "project", UID: "zone-uid"}, Spec: dnsv1alpha1.DNSZoneSpec{DomainName: "datum.internal", Visibility: dnsv1alpha1.DNSZoneVisibilityPrivate}} + registration := &dnsv1alpha1.DNSRegistration{ObjectMeta: metav1.ObjectMeta{Name: "web", Namespace: "project", UID: "registration", Generation: 1}, Spec: dnsv1alpha1.DNSRegistrationSpec{DNSZoneRef: dnsv1alpha1.DNSObjectReference{Name: zone.Name, UID: zone.UID}, Name: "web", RecordTypes: []dnsv1alpha1.RRType{dnsv1alpha1.RRTypeA}}} + handler := Handler{Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(zone, registration).Build()} + contribution := &dnsv1alpha1.DNSRecordContribution{Spec: dnsv1alpha1.DNSRecordContributionSpec{RegistrationRef: dnsv1alpha1.DNSObjectReference{Name: registration.Name, UID: registration.UID, Generation: registration.Generation}, RecordSets: []dnsv1alpha1.DNSContributionRecordSet{{RecordType: dnsv1alpha1.RRTypeA, Records: []dnsv1alpha1.RecordEntry{{Name: "web", A: &dnsv1alpha1.ARecordSpec{Content: "10.0.0.8"}}}}}}} + for _, caller := range []string{"compute-publisher", "connect-publisher"} { + if result := handler.Handle(context.Background(), request("dnsrecordcontributions", caller, contribution, nil)); !result.Allowed { + t.Fatalf("valid contribution rejected for %s: %v", caller, result.Result) + } + } + contribution.Spec.RegistrationRef.UID = "replacement" + if result := handler.Handle(context.Background(), request("dnsrecordcontributions", "compute-publisher", contribution, nil)); result.Allowed { + t.Fatal("contribution accepted a mismatched registration lifetime") + } +} + +func TestDeletingResourceCanRemoveFinalizersWithoutReferences(t *testing.T) { + h := Handler{} + for _, resource := range []string{"dnsregistrations", "dnsrecordcontributions", "dnszoneassociations", "dnsnamingpolicies", "dnsresolveraccessbindings"} { + object := metav1.PartialObjectMetadata{ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &metav1.Time{Time: time.Now()}}} + if res := h.Handle(context.Background(), request(resource, "publisher", object, object)); !res.Allowed { + t.Fatalf("%s finalization required live references: %v", resource, res.Result) + } + } +} diff --git a/internal/internaldns/model/model.go b/internal/internaldns/model/model.go new file mode 100644 index 00000000..1f613b98 --- /dev/null +++ b/internal/internaldns/model/model.go @@ -0,0 +1,543 @@ +// Package model defines the durable wire contract shared by the internal DNS +// control plane and every regional serving replica. +package model + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "net/netip" + "regexp" + "sort" + "strings" + "time" + + "github.com/miekg/dns" +) + +const APIVersion = "internal.dns.networking.miloapis.com/v1alpha6" + +type Kind string + +const ( + KindServingSnapshot Kind = "ServingSnapshot" + KindPublicationChunk Kind = "PublicationChunk" + KindPublicationManifest Kind = "PublicationManifest" + KindMemberAck Kind = "MemberAck" +) + +// Envelope is the only value written to JetStream. Epoch and Revision are +// repeated outside Payload so consumers can reject stale events before decode. +type Envelope struct { + APIVersion string `json:"apiVersion"` + Kind Kind `json:"kind"` + EventID string `json:"eventID"` + Region string `json:"region"` + Shard string `json:"shard"` + ResourceUID string `json:"resourceUID"` + Epoch uint64 `json:"epoch"` + Revision uint64 `json:"revision"` + PublishedAt time.Time `json:"publishedAt"` + Payload json.RawMessage `json:"payload"` +} + +func NewEnvelope(kind Kind, eventID, region, shard, uid string, epoch, revision uint64, at time.Time, payload any) (Envelope, error) { + b, err := json.Marshal(payload) + if err != nil { + return Envelope{}, fmt.Errorf("marshal %s payload: %w", kind, err) + } + e := Envelope{APIVersion: APIVersion, Kind: kind, EventID: eventID, Region: region, Shard: shard, ResourceUID: uid, Epoch: epoch, Revision: revision, PublishedAt: at.UTC(), Payload: b} + return e, e.Validate() +} + +func (e Envelope) Validate() error { + if e.APIVersion != APIVersion { + return fmt.Errorf("unsupported apiVersion %q", e.APIVersion) + } + switch e.Kind { + case KindServingSnapshot, KindPublicationChunk, KindPublicationManifest, KindMemberAck: + default: + return fmt.Errorf("unsupported kind %q", e.Kind) + } + if e.EventID == "" || e.Region == "" || e.Shard == "" || e.ResourceUID == "" || e.Epoch == 0 || e.Revision == 0 || e.PublishedAt.IsZero() || len(e.Payload) == 0 { + return errors.New("envelope identity, epoch, revision, timestamp, and payload are required") + } + if SafeToken(e.Region) != e.Region || SafeToken(e.Shard) != e.Shard { + return errors.New("region and shard must be safe lowercase identifiers") + } + return nil +} + +type Transport string + +const ( + TransportUDP Transport = "UDP" + TransportTCP Transport = "TCP" +) + +type AccessVersion struct { + Epoch uint64 `json:"epoch"` + Sequence uint64 `json:"sequence"` +} + +type Backend struct { + MemberID string `json:"memberID"` + Address string `json:"address"` + Port uint16 `json:"port"` +} + +type ZoneAttachment struct { + ZoneUID string `json:"zoneUID"` + Apex string `json:"apex"` + RequiredPublicationEpoch uint64 `json:"requiredPublicationEpoch"` + RequiredPublicationRevision uint64 `json:"requiredPublicationRevision"` +} + +// Binding is a logical cache and policy context in shared processes. +type Binding struct { + BindingUID string `json:"bindingUID"` + ProjectUID string `json:"projectUID"` + ContextUID string `json:"contextUID"` + BindingGeneration uint64 `json:"bindingGeneration"` + ConfigurationRevision uint64 `json:"configurationRevision"` + ConsumerAddress string `json:"consumerAddress"` + ClusterAddress string `json:"clusterAddress"` + Port uint16 `json:"port"` + Transports []Transport `json:"transports"` + Zones []ZoneAttachment `json:"zones"` + NodeBackends []Backend `json:"nodeBackends"` + ClusterBackends []Backend `json:"clusterBackends"` + Version AccessVersion `json:"version"` + Tombstone bool `json:"tombstone,omitempty"` +} + +func (b Binding) ViewName() string { + return "context_" + OpaqueToken(b.ProjectUID+"/"+b.ContextUID) + "_g" + fmt.Sprint(b.BindingGeneration) +} + +type Member struct { + MemberID string `json:"memberID"` + ReplicaID string `json:"replicaID,omitempty"` + Role string `json:"role"` +} + +type PublicationRequirement struct { + ZoneUID string `json:"zoneUID"` + Epoch uint64 `json:"epoch"` + Revision uint64 `json:"revision"` +} + +// ServingSnapshot is complete for a shard. Omission therefore means removal, +// but only after the entire snapshot has passed validation and been staged. +type ServingSnapshot struct { + Region string `json:"region"` + Shard string `json:"shard"` + ConfigurationEpoch uint64 `json:"configurationEpoch"` + ConfigurationRevision uint64 `json:"configurationRevision"` + Bindings []Binding `json:"bindings"` + Members []Member `json:"members"` + RequiredPublications []PublicationRequirement `json:"requiredPublications,omitempty"` + GeneratedAt time.Time `json:"generatedAt"` +} + +func (s ServingSnapshot) Validate() error { + if s.Region == "" || s.Shard == "" || s.ConfigurationEpoch == 0 || s.ConfigurationRevision == 0 || s.GeneratedAt.IsZero() { + return errors.New("serving snapshot identity, epoch, revision, and timestamp are required") + } + + seenVIP := map[string]string{} + seenUID := map[string]struct{}{} + seenMembers := map[string]struct{}{} + for _, member := range s.Members { + if member.MemberID == "" || (member.Role != "resolver" && member.Role != "node" && member.Role != "cluster" && member.Role != "regional") { + return fmt.Errorf("invalid serving member %q with role %q", member.MemberID, member.Role) + } + key := member.MemberID + "\x00" + member.ReplicaID + if _, ok := seenMembers[key]; ok { + return fmt.Errorf("duplicate serving member %q replica %q", member.MemberID, member.ReplicaID) + } + seenMembers[key] = struct{}{} + } + seenRequirements := map[string]struct{}{} + for _, requirement := range s.RequiredPublications { + if requirement.ZoneUID == "" || requirement.Epoch == 0 || requirement.Revision == 0 { + return errors.New("required publication has incomplete identity") + } + if _, ok := seenRequirements[requirement.ZoneUID]; ok { + return fmt.Errorf("duplicate required publication %q", requirement.ZoneUID) + } + seenRequirements[requirement.ZoneUID] = struct{}{} + } + for i, b := range s.Bindings { + if b.BindingUID == "" || b.ProjectUID == "" || b.ContextUID == "" || b.BindingGeneration == 0 || b.ConfigurationRevision == 0 { + return fmt.Errorf("binding %d has incomplete identity", i) + } + if _, ok := seenUID[b.BindingUID]; ok { + return fmt.Errorf("duplicate binding UID %q", b.BindingUID) + } + seenUID[b.BindingUID] = struct{}{} + for name, raw := range map[string]string{"consumerAddress": b.ConsumerAddress, "clusterAddress": b.ClusterAddress} { + addr, err := netip.ParseAddr(raw) + if err != nil || addr.IsUnspecified() { + return fmt.Errorf("binding %q invalid %s %q", b.BindingUID, name, raw) + } + if old, ok := seenVIP[raw]; ok && old != b.BindingUID { + return fmt.Errorf("address %s assigned to bindings %q and %q", raw, old, b.BindingUID) + } + seenVIP[raw] = b.BindingUID + } + if b.Port == 0 || len(b.Transports) == 0 || b.Version.Epoch == 0 || b.Version.Sequence == 0 { + return fmt.Errorf("binding %q has incomplete transport or access version", b.BindingUID) + } + if err := validateTransports(b.Transports); err != nil { + return fmt.Errorf("binding %q: %w", b.BindingUID, err) + } + for _, group := range [][]Backend{b.NodeBackends, b.ClusterBackends} { + backendMembers := map[string]string{} + backendAddresses := map[string]string{} + for _, backend := range group { + if backend.MemberID == "" || backend.Port == 0 { + return fmt.Errorf("binding %q has incomplete backend", b.BindingUID) + } + if addr, err := netip.ParseAddr(backend.Address); err != nil || addr.IsUnspecified() { + return fmt.Errorf("binding %q has invalid backend address %q", b.BindingUID, backend.Address) + } + endpoint := netip.MustParseAddr(backend.Address).String() + ":" + fmt.Sprint(backend.Port) + if old, ok := backendMembers[backend.MemberID]; ok && old != endpoint { + return fmt.Errorf("binding %q backend member %q has conflicting endpoints", b.BindingUID, backend.MemberID) + } + if old, ok := backendAddresses[endpoint]; ok && old != backend.MemberID { + return fmt.Errorf("binding %q backend endpoint %q has conflicting member IDs", b.BindingUID, endpoint) + } + backendMembers[backend.MemberID] = endpoint + backendAddresses[endpoint] = backend.MemberID + } + } + zoneApex := map[string]string{} + for _, z := range b.Zones { + if z.ZoneUID == "" || z.RequiredPublicationEpoch == 0 || z.RequiredPublicationRevision == 0 { + return fmt.Errorf("binding %q has incomplete zone attachment", b.BindingUID) + } + apex := AbsoluteName(z.Apex) + if _, ok := dns.IsDomainName(apex); apex == "." || !ok { + return fmt.Errorf("binding %q has invalid zone apex", b.BindingUID) + } + if old, ok := zoneApex[apex]; ok && old != z.ZoneUID { + return fmt.Errorf("binding %q attaches zones %q and %q at %s", b.BindingUID, old, z.ZoneUID, apex) + } + zoneApex[apex] = z.ZoneUID + } + } + return nil +} + +func validateTransports(ts []Transport) error { + seen := map[Transport]bool{} + for _, t := range ts { + if t != TransportUDP && t != TransportTCP { + return fmt.Errorf("unsupported transport %q", t) + } + if seen[t] { + return fmt.Errorf("duplicate transport %q", t) + } + seen[t] = true + } + return nil +} + +type ChunkRef struct { + Index int `json:"index"` + SHA256 string `json:"sha256"` + Size int `json:"size"` +} + +type PublicationManifest struct { + ManifestUID string `json:"manifestUID"` + ZoneUID string `json:"zoneUID"` + Apex string `json:"apex"` + WriterEpoch uint64 `json:"writerEpoch"` + Revision uint64 `json:"revision"` + PreviousRevision uint64 `json:"previousRevision,omitempty"` + Tombstone bool `json:"tombstone,omitempty"` + Chunks []ChunkRef `json:"chunks"` + ContentHash string `json:"contentHash"` + GeneratedAt time.Time `json:"generatedAt"` +} + +func (m PublicationManifest) Validate() error { + if m.ManifestUID == "" || m.ZoneUID == "" || AbsoluteName(m.Apex) == "." || m.WriterEpoch == 0 || m.Revision == 0 || m.GeneratedAt.IsZero() { + return errors.New("publication manifest identity, zone, epoch, revision, and timestamp are required") + } + if m.PreviousRevision >= m.Revision && m.PreviousRevision != 0 { + return errors.New("publication manifest previous revision must precede revision") + } + if _, ok := dns.IsDomainName(AbsoluteName(m.Apex)); !ok { + return errors.New("publication manifest apex is invalid") + } + if m.Tombstone { + if len(m.Chunks) != 0 || m.ContentHash != EmptyContentHash() { + return errors.New("tombstone must have no chunks and the empty content hash") + } + return nil + } + if len(m.Chunks) == 0 || !validHash(m.ContentHash) { + return errors.New("non-tombstone manifest requires chunks and a SHA-256 content hash") + } + refs := append([]ChunkRef(nil), m.Chunks...) + sort.Slice(refs, func(i, j int) bool { return refs[i].Index < refs[j].Index }) + for i, ref := range refs { + if ref.Index != i || ref.Size <= 0 || !validHash(ref.SHA256) { + return fmt.Errorf("invalid chunk reference at ordered index %d", i) + } + } + return nil +} + +type PublicationChunk struct { + ManifestUID string `json:"manifestUID"` + ZoneUID string `json:"zoneUID"` + WriterEpoch uint64 `json:"writerEpoch"` + Revision uint64 `json:"revision"` + Index int `json:"index"` + SHA256 string `json:"sha256"` + Payload []byte `json:"payload"` +} + +func (c PublicationChunk) Validate() error { + if c.ManifestUID == "" || c.ZoneUID == "" || c.WriterEpoch == 0 || c.Revision == 0 || c.Index < 0 || len(c.Payload) == 0 || !validHash(c.SHA256) { + return errors.New("publication chunk identity, revision, hash, and payload are required") + } + if Hash(c.Payload) != c.SHA256 { + return errors.New("publication chunk SHA-256 mismatch") + } + return nil +} + +type RRRecord struct { + Content string `json:"content"` + ContributionUID string `json:"contributionUID,omitempty"` + Generation uint64 `json:"generation,omitempty"` + Sequence uint64 `json:"sequence,omitempty"` + Eligible bool `json:"eligible"` +} + +type RRSet struct { + Name string `json:"name"` + Type string `json:"type"` + TTL uint32 `json:"ttl"` + Records []RRRecord `json:"records"` +} + +type PublicationPlan struct { + ZoneUID string `json:"zoneUID"` + Apex string `json:"apex"` + Registrations []RegistrationFence `json:"registrations,omitempty"` + ObservationFences []ObservationFence `json:"observationFences,omitempty"` + Owners []string `json:"owners,omitempty"` + RRSets []RRSet `json:"rrsets"` +} + +type RegistrationFence struct { + UID string `json:"uid"` + Generation uint64 `json:"generation"` +} + +// ObservationFence carries the accepted high-water mark, including withdrawn observations. +type ObservationFence struct { + ContributionUID string `json:"contributionUID"` + Generation uint64 `json:"generation"` + Sequence uint64 `json:"sequence"` + Retired bool `json:"retired,omitempty"` + SourceName string `json:"sourceName,omitempty"` +} + +func (p PublicationPlan) Validate() error { + if p.ZoneUID == "" || AbsoluteName(p.Apex) == "." { + return errors.New("publication plan zone identity is required") + } + apex := AbsoluteName(p.Apex) + if _, ok := dns.IsDomainName(apex); !ok { + return errors.New("publication plan apex is invalid") + } + previousRegistration := "" + for _, registration := range p.Registrations { + if registration.UID == "" || registration.Generation == 0 { + return errors.New("publication registration fence is incomplete") + } + if previousRegistration != "" && registration.UID <= previousRegistration { + return errors.New("publication registration fences must be unique and sorted by UID") + } + previousRegistration = registration.UID + } + observationByUID := make(map[string]ObservationFence, len(p.ObservationFences)) + previousContribution := "" + for _, observation := range p.ObservationFences { + if observation.ContributionUID == "" || observation.Generation == 0 || observation.Sequence == 0 { + return errors.New("publication observation fence is incomplete") + } + if previousContribution != "" && observation.ContributionUID <= previousContribution { + return errors.New("publication observation fences must be unique and sorted by contribution UID") + } + previousContribution = observation.ContributionUID + observationByUID[observation.ContributionUID] = observation + } + for _, owner := range p.Owners { + owner = AbsoluteName(owner) + if _, ok := dns.IsDomainName(owner); !ok || !dns.IsSubDomain(apex, owner) { + return fmt.Errorf("owned name %q is outside zone %q", owner, apex) + } + } + seen := map[string]struct{}{} + for _, rr := range p.RRSets { + owner := strings.ToLower(AbsoluteName(rr.Name)) + rrType := strings.ToUpper(rr.Type) + key := owner + "\x00" + rrType + if rr.TTL == 0 || len(rr.Records) == 0 || rr.Type == "" { + return fmt.Errorf("RRset %q is incomplete", key) + } + if _, ok := dns.IsDomainName(owner); !ok || !dns.IsSubDomain(apex, owner) { + return fmt.Errorf("RRset owner %q is outside zone %q", owner, apex) + } + if _, ok := dns.StringToType[rrType]; !ok { + return fmt.Errorf("RRset %q has unsupported type %q", owner, rr.Type) + } + if _, ok := seen[key]; ok { + return fmt.Errorf("duplicate RRset %q", key) + } + seen[key] = struct{}{} + for _, rec := range rr.Records { + if rec.Content == "" { + return fmt.Errorf("RRset %q contains an invalid record", key) + } + if rec.ContributionUID == "" { + if rec.Generation != 0 || rec.Sequence != 0 { + return fmt.Errorf("RRset %q static record contains contribution fence data", key) + } + } else { + observation, ok := observationByUID[rec.ContributionUID] + if !ok || observation.Retired || rec.Generation != observation.Generation || rec.Sequence != observation.Sequence { + return fmt.Errorf("RRset %q record fence does not match contribution observation %q", key, rec.ContributionUID) + } + } + if _, err := dns.NewRR(fmt.Sprintf("%s %d IN %s %s", owner, rr.TTL, rrType, rec.Content)); err != nil { + return fmt.Errorf("RRset %q contains invalid RDATA: %w", key, err) + } + } + } + return nil +} + +type AckPhase string + +const ( + AckAccepted AckPhase = "Accepted" + AckApplied AckPhase = "Applied" + AckVerified AckPhase = "Verified" + AckExpired AckPhase = "Expired" + AckRejected AckPhase = "Rejected" +) + +type MemberAck struct { + MemberID string `json:"memberID"` + ReplicaID string `json:"replicaID,omitempty"` + ResourceUID string `json:"resourceUID"` + Kind Kind `json:"kind"` + Epoch uint64 `json:"epoch"` + Revision uint64 `json:"revision"` + Phase AckPhase `json:"phase"` + ObservedAt time.Time `json:"observedAt"` + ValidUntil time.Time `json:"validUntil,omitempty"` + Error string `json:"error,omitempty"` +} + +func Hash(b []byte) string { + sum := sha256.Sum256(b) + return hex.EncodeToString(sum[:]) +} + +func EmptyContentHash() string { return Hash(nil) } + +func VerifyManifest(m PublicationManifest, chunks []PublicationChunk) ([]byte, error) { + if err := m.Validate(); err != nil { + return nil, err + } + if m.Tombstone { + return nil, nil + } + if len(chunks) != len(m.Chunks) { + return nil, fmt.Errorf("manifest references %d chunks but %d were supplied", len(m.Chunks), len(chunks)) + } + byIndex := make(map[int]PublicationChunk, len(chunks)) + for _, c := range chunks { + if err := c.Validate(); err != nil { + return nil, err + } + if c.ManifestUID != m.ManifestUID || c.ZoneUID != m.ZoneUID || c.WriterEpoch != m.WriterEpoch || c.Revision != m.Revision { + return nil, fmt.Errorf("chunk %d identity does not match manifest", c.Index) + } + if _, exists := byIndex[c.Index]; exists { + return nil, fmt.Errorf("duplicate chunk index %d", c.Index) + } + byIndex[c.Index] = c + } + var payload []byte + for _, ref := range m.Chunks { + c, ok := byIndex[ref.Index] + if !ok || len(c.Payload) != ref.Size || c.SHA256 != ref.SHA256 { + return nil, fmt.Errorf("chunk %d is missing or differs from manifest", ref.Index) + } + payload = append(payload, c.Payload...) + } + if Hash(payload) != m.ContentHash { + return nil, errors.New("publication content SHA-256 mismatch") + } + return payload, nil +} + +func Compare(epoch, revision, currentEpoch, currentRevision uint64) int { + if epoch < currentEpoch || (epoch == currentEpoch && revision < currentRevision) { + return -1 + } + if epoch == currentEpoch && revision == currentRevision { + return 0 + } + return 1 +} + +func AbsoluteName(s string) string { + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" { + return "." + } + if !strings.HasSuffix(s, ".") { + s += "." + } + return s +} + +var unsafeToken = regexp.MustCompile(`[^a-z0-9_-]+`) + +func SafeToken(s string) string { + s = unsafeToken.ReplaceAllString(strings.ToLower(s), "_") + s = strings.Trim(s, "_") + if s == "" { + return "unknown" + } + return s +} + +// OpaqueToken avoids placing customer-controlled identifiers in NATS subjects. +func OpaqueToken(uid string) string { + sum := sha256.Sum256([]byte(uid)) + return hex.EncodeToString(sum[:12]) +} + +func validHash(s string) bool { + if len(s) != sha256.Size*2 { + return false + } + _, err := hex.DecodeString(s) + return err == nil +} diff --git a/internal/internaldns/model/model_test.go b/internal/internaldns/model/model_test.go new file mode 100644 index 00000000..470101be --- /dev/null +++ b/internal/internaldns/model/model_test.go @@ -0,0 +1,93 @@ +package model + +import ( + "encoding/json" + "testing" + "time" +) + +func TestVerifyManifestRequiresEveryExactChunk(t *testing.T) { + t.Parallel() + plan := PublicationPlan{ZoneUID: "zone-a", Apex: "internal.example", RRSets: []RRSet{{Name: "api.internal.example", Type: "A", TTL: 30, Records: []RRRecord{{Content: "192.0.2.1", Eligible: true}}}}} + payload, err := json.Marshal(plan) + if err != nil { + t.Fatal(err) + } + cut := len(payload) / 2 + parts := [][]byte{payload[:cut], payload[cut:]} + chunks := make([]PublicationChunk, 0, 2) + refs := make([]ChunkRef, 0, 2) + for i, p := range parts { + chunks = append(chunks, PublicationChunk{ManifestUID: "manifest-a", ZoneUID: "zone-a", WriterEpoch: 3, Revision: 9, Index: i, SHA256: Hash(p), Payload: p}) + refs = append(refs, ChunkRef{Index: i, SHA256: Hash(p), Size: len(p)}) + } + m := PublicationManifest{ManifestUID: "manifest-a", ZoneUID: "zone-a", Apex: "internal.example", WriterEpoch: 3, Revision: 9, Chunks: refs, ContentHash: Hash(payload), GeneratedAt: time.Now()} + got, err := VerifyManifest(m, chunks) + if err != nil { + t.Fatal(err) + } + if string(got) != string(payload) { + t.Fatalf("payload differs") + } + if _, err := VerifyManifest(m, chunks[:1]); err == nil { + t.Fatal("missing chunk accepted") + } + chunks[1].Payload[0] ^= 1 + if _, err := VerifyManifest(m, chunks); err == nil { + t.Fatal("corrupt chunk accepted") + } +} + +func TestServingSnapshotRejectsSharedDestination(t *testing.T) { + t.Parallel() + now := time.Now() + b := func(uid, vpc string) Binding { + return Binding{BindingUID: uid, ProjectUID: "project", ContextUID: vpc, BindingGeneration: 1, ConfigurationRevision: 1, ConsumerAddress: "2001:db8::1", ClusterAddress: "2001:db8::2", Port: 53, Transports: []Transport{TransportUDP, TransportTCP}, Version: AccessVersion{Epoch: 1, Sequence: 1}} + } + s := ServingSnapshot{Region: "east", Shard: "s1", ConfigurationEpoch: 1, ConfigurationRevision: 1, GeneratedAt: now, Bindings: []Binding{b("one", "vpc-a"), b("two", "vpc-b")}} + if err := s.Validate(); err == nil { + t.Fatal("duplicate trusted destination accepted") + } +} + +func TestServingSnapshotRejectsUnknownMemberRole(t *testing.T) { + s := ServingSnapshot{Region: "east", Shard: "s1", ConfigurationEpoch: 1, ConfigurationRevision: 1, GeneratedAt: time.Now(), Members: []Member{{MemberID: "old", Role: "unassigned"}}} + if s.Validate() == nil { + t.Fatal("unknown serving role accepted") + } +} + +func TestCompareFencesOldEpoch(t *testing.T) { + t.Parallel() + if Compare(2, 1000, 3, 1) >= 0 { + t.Fatal("old epoch won by revision") + } + if Compare(4, 1, 3, 1000) <= 0 { + t.Fatal("new epoch lost") + } +} + +func TestViewNameScopesContextByProject(t *testing.T) { + a := Binding{ProjectUID: "project-a", ContextUID: "remote-context", BindingGeneration: 1} + b := a + b.ProjectUID = "project-b" + if a.ViewName() == b.ViewName() { + t.Fatal("identical remote context UID shared a cache namespace across projects") + } + b = a + b.BindingGeneration = 2 + if a.ViewName() == b.ViewName() { + t.Fatal("new binding generation reused an old cache namespace") + } +} + +func TestEnvelopeRejectsObsoleteWireVersion(t *testing.T) { + current, err := NewEnvelope(KindMemberAck, "event", "central", "shared", "uid", 1, 1, time.Now(), MemberAck{MemberID: "member"}) + if err != nil { + t.Fatal(err) + } + current.APIVersion = "internal.dns.networking.miloapis.com/v1alpha1" + if current.Validate() == nil { + t.Fatal("obsolete transport version accepted") + } +} diff --git a/internal/internaldns/model/private_records.go b/internal/internaldns/model/private_records.go new file mode 100644 index 00000000..e0a6d9af --- /dev/null +++ b/internal/internaldns/model/private_records.go @@ -0,0 +1,22 @@ +package model + +import ( + "fmt" + "strings" +) + +// ValidatePrivateRecordOwner reserves the authority metadata generated by the +// regional BIND fleet. It applies only to internal DNS publication inputs. +func ValidatePrivateRecordOwner(owner, apex, recordType string) error { + owner = strings.TrimSpace(owner) + if strings.EqualFold(recordType, "ALIAS") { + return fmt.Errorf("private DNS does not support ALIAS flattening") + } + if strings.EqualFold(recordType, "SOA") { + return fmt.Errorf("private zone SOA records are managed by the DNS service") + } + if strings.EqualFold(recordType, "CNAME") && (owner == "" || owner == "@" || AbsoluteName(owner) == AbsoluteName(apex)) { + return fmt.Errorf("a private zone apex cannot be a CNAME") + } + return nil +} diff --git a/internal/internaldns/model/snapshot_keys.go b/internal/internaldns/model/snapshot_keys.go new file mode 100644 index 00000000..9a6214d4 --- /dev/null +++ b/internal/internaldns/model/snapshot_keys.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package model + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "strings" +) + +func SnapshotPrefix(region, shard string) string { + return SafeToken(region) + "." + SafeToken(shard) + "." +} + +func PublicationActivationName(manifest, region, shard string) string { + name := manifest + "-" + SafeToken(region) + "-" + SafeToken(shard) + "-activate" + if len(name) <= 63 { + return strings.Trim(name, "-") + } + sum := sha256.Sum256([]byte(name)) + return strings.Trim(name[:50], "-") + fmt.Sprintf("-%x", sum[:6]) +} + +// Chunk keys retain a complete immutable revision until its manifest head has +// moved and the supported replay window has elapsed. A manifest head is written +// only after every referenced chunk is durable in both JetStream and bootstrap. +func SnapshotKey(env Envelope) (string, error) { + prefix := SnapshotPrefix(env.Region, env.Shard) + uid := OpaqueToken(env.ResourceUID) + switch env.Kind { + case KindPublicationChunk: + var c PublicationChunk + if err := json.Unmarshal(env.Payload, &c); err != nil { + return "", err + } + return fmt.Sprintf("%schunk.%s.e%d.r%d.i%d", prefix, uid, env.Epoch, env.Revision, c.Index), nil + case KindPublicationManifest: + return prefix + "manifest." + uid, nil + case KindServingSnapshot: + return prefix + "serving." + uid, nil + default: + return "", fmt.Errorf("event %s is not bootstrap state", env.Kind) + } +} diff --git a/internal/internaldns/model/subjects.go b/internal/internaldns/model/subjects.go new file mode 100644 index 00000000..783f0db0 --- /dev/null +++ b/internal/internaldns/model/subjects.go @@ -0,0 +1,23 @@ +package model + +import "fmt" + +func ServingSubject(region, shard string) string { + return fmt.Sprintf("dns.private.serving.%s.%s", SafeToken(region), SafeToken(shard)) +} + +func RecordChunkSubject(region, shard, zoneUID string) string { + return fmt.Sprintf("dns.private.records.%s.%s.%s.chunk", SafeToken(region), SafeToken(shard), OpaqueToken(zoneUID)) +} + +func RecordManifestSubject(region, shard, zoneUID string) string { + return fmt.Sprintf("dns.private.records.%s.%s.%s.manifest", SafeToken(region), SafeToken(shard), OpaqueToken(zoneUID)) +} + +func RecordConsumerFilter(region, shard string) string { + return fmt.Sprintf("dns.private.records.%s.%s.>", SafeToken(region), SafeToken(shard)) +} + +func AckSubject(region, shard, memberID string) string { + return fmt.Sprintf("dns.private.acks.%s.%s.%s", SafeToken(region), SafeToken(shard), OpaqueToken(memberID)) +}