Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59af24f695 | |||
| 8b7df15883 | |||
| c6400c7773 | |||
| 97b2545e2d |
9 changed files with 251 additions and 24 deletions
22
.drone-manifest.yml
Normal file
22
.drone-manifest.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
image: happydomain/checker-caa:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
- image: happydomain/checker-caa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: happydomain/checker-caa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
- image: happydomain/checker-caa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
variant: v7
|
||||
187
.drone.yml
Normal file
187
.drone.yml
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-amd64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: checker build
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: checker build tag
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-caa
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub (tag)
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-caa
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
exclude:
|
||||
- renovate/*
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-arm64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: checker build
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: checker build tag
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-caa
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub (tag)
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-caa
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: docker-manifest
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: .drone-manifest.yml
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
exclude:
|
||||
- renovate/*
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
depends_on:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
19
README.md
19
README.md
|
|
@ -25,22 +25,11 @@ Identifiers" mapping.
|
|||
- compares the observed identifiers against the `issue` /
|
||||
`issuewild` allow list (or flags a `DisallowIssue` violation).
|
||||
|
||||
## Observation payload
|
||||
## Rules
|
||||
|
||||
This checker does not publish endpoints or add a new observation
|
||||
schema. Under its own observation key `caa_policy` it returns a
|
||||
pass-through view of the zone-side CAA records:
|
||||
|
||||
```json
|
||||
{
|
||||
"domain": "example.net",
|
||||
"records": [
|
||||
{ "flag": 0, "tag": "issue", "value": "letsencrypt.org" },
|
||||
{ "flag": 0, "tag": "issuewild", "value": ";" }
|
||||
],
|
||||
"run_at": "2026-04-22T12:34:56Z"
|
||||
}
|
||||
```
|
||||
| Code | Description | Severity |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------------|----------|
|
||||
| `caa_compliance` | Cross-references TLS certificates observed on the domain against its CAA `issue`/`issuewild` policy, mapping each observed issuer to its CCADB-published CAA identifier. | CRITICAL |
|
||||
|
||||
## Rule outcomes
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
|
@ -64,7 +63,6 @@ func (p *caaProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any
|
|||
return &CAAData{
|
||||
Domain: domain,
|
||||
Records: records,
|
||||
RunAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
26
checker/discover.go
Normal file
26
checker/discover.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
tlscontract "git.happydns.org/checker-tls/contract"
|
||||
)
|
||||
|
||||
// DiscoverEntries publishes one tls.endpoint.v1 entry for the domain's HTTPS
|
||||
// endpoint so checker-tls probes it. Implements sdk.DiscoveryPublisher.
|
||||
// On the next checker-tls run the engine will store a DiscoveryObservationRef
|
||||
// linking its snapshot back to this checker; GetRelated("tls_probes") in the
|
||||
// rule will then return the observed certificates.
|
||||
func (p *caaProvider) DiscoverEntries(data any) ([]sdk.DiscoveryEntry, error) {
|
||||
d, ok := data.(*CAAData)
|
||||
if !ok || d == nil || d.Domain == "" {
|
||||
return nil, nil
|
||||
}
|
||||
entry, err := tlscontract.NewEntry(tlscontract.TLSEndpoint{
|
||||
Host: d.Domain,
|
||||
Port: 443,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []sdk.DiscoveryEntry{entry}, nil
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ type issuerAgg struct {
|
|||
code string
|
||||
msg string
|
||||
endpoints map[string]bool
|
||||
count int // number of certificates observed from this issuer
|
||||
}
|
||||
|
||||
type allowList struct {
|
||||
|
|
@ -152,6 +153,7 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts
|
|||
cur = &issuerAgg{sample: p, endpoints: map[string]bool{}}
|
||||
agg[k] = cur
|
||||
}
|
||||
cur.count++
|
||||
if severityRank(severity) >= severityRank(cur.severity) {
|
||||
cur.severity = severity
|
||||
cur.code = code
|
||||
|
|
@ -233,22 +235,23 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts
|
|||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
sort.Strings(endpoints)
|
||||
meta := map[string]any{"endpoints": endpoints}
|
||||
meta := map[string]any{"endpoints": endpoints, "cert_count": a.count}
|
||||
|
||||
certSuffix := fmt.Sprintf(" (%d certificate(s) checked)", a.count)
|
||||
switch a.severity {
|
||||
case SeverityCrit:
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusCrit, Message: a.msg, Code: a.code,
|
||||
Status: sdk.StatusCrit, Message: a.msg + certSuffix, Code: a.code,
|
||||
Subject: subject, Meta: meta,
|
||||
})
|
||||
case SeverityWarn:
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusWarn, Message: a.msg, Code: a.code,
|
||||
Status: sdk.StatusWarn, Message: a.msg + certSuffix, Code: a.code,
|
||||
Subject: subject, Meta: meta,
|
||||
})
|
||||
case SeverityInfo:
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusInfo, Message: a.msg, Code: a.code,
|
||||
Status: sdk.StatusInfo, Message: a.msg + certSuffix, Code: a.code,
|
||||
Subject: subject, Meta: meta,
|
||||
})
|
||||
default:
|
||||
|
|
@ -257,7 +260,7 @@ func (r *caaRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts
|
|||
msg = "Certificate observed; no CAA records published"
|
||||
}
|
||||
out = append(out, sdk.CheckState{
|
||||
Status: sdk.StatusOK, Message: msg, Code: CodeOK,
|
||||
Status: sdk.StatusOK, Message: msg + certSuffix, Code: CodeOK,
|
||||
Subject: subject, Meta: meta,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ const (
|
|||
type CAAData struct {
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Records []CAARecord `json:"records,omitempty"`
|
||||
RunAt string `json:"run_at,omitempty"`
|
||||
}
|
||||
|
||||
type CAARecord struct {
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -4,6 +4,7 @@ go 1.25.0
|
|||
|
||||
require (
|
||||
git.happydns.org/checker-sdk-go v1.5.0
|
||||
git.happydns.org/checker-tls v0.6.2
|
||||
github.com/miekg/dns v1.1.72
|
||||
)
|
||||
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,5 +1,7 @@
|
|||
git.happydns.org/checker-sdk-go v1.5.0 h1:5uD5Cm6xJ+lwnhbJ09iCXGHbYS9zRh+Yh0NeBHkAPBY=
|
||||
git.happydns.org/checker-sdk-go v1.5.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
||||
git.happydns.org/checker-tls v0.6.2 h1:8oKia1XlD+tklyqrwzmUgFH1Kw8VLSLLF9suZ7Qr14E=
|
||||
git.happydns.org/checker-tls v0.6.2/go.mod h1:9tpnxg0iOwS+7If64DRG1jqYonUAgxOBuxwfF5mVkL4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue