diff --git a/decnet/templates/_caddy_modules/decnetfp/go.mod b/decnet/templates/_caddy_modules/decnetfp/go.mod index 958acf65..1759f457 100644 --- a/decnet/templates/_caddy_modules/decnetfp/go.mod +++ b/decnet/templates/_caddy_modules/decnetfp/go.mod @@ -4,6 +4,113 @@ go 1.22 require ( github.com/caddyserver/caddy/v2 v2.8.4 + github.com/quic-go/quic-go v0.44.0 go.uber.org/zap v1.27.0 golang.org/x/net v0.27.0 ) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/certmagic v0.21.3 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/cel-go v0.20.1 // indirect + github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/libdns/libdns v0.2.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mholt/acmez/v2 v2.0.1 // indirect + github.com/miekg/dns v1.1.59 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/slackhq/nebula v1.6.1 // indirect + github.com/smallstep/certificates v0.26.1 // indirect + github.com/smallstep/nosql v0.6.1 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/smallstep/truststore v0.13.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect + github.com/urfave/cli v1.22.14 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 // indirect + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/mock v0.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.2.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect +) diff --git a/decnet/templates/_caddy_modules/decnetfp/go.sum b/decnet/templates/_caddy_modules/decnetfp/go.sum new file mode 100644 index 00000000..c902cb6b --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/go.sum @@ -0,0 +1,592 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk= +github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw= +github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= +github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= +github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= +github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= +github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/decnet/templates/_caddy_modules/decnetfp/h1_test.go b/decnet/templates/_caddy_modules/decnetfp/h1_test.go new file mode 100644 index 00000000..7d8e5ecf --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/h1_test.go @@ -0,0 +1,150 @@ +package decnetfp + +import ( + "encoding/json" + "net" + "testing" + "time" +) + +// bindSock creates a unix datagram socket at path and returns it. +// The caller must close it (or register a t.Cleanup). +func bindSock(t *testing.T, path string) *net.UnixConn { + t.Helper() + sock, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: path, Net: "unixgram"}) + if err != nil { + t.Fatalf("listen %s: %v", path, err) + } + return sock +} + +// drainSock reads up to n records from an already-bound unix datagram socket. +func drainSock(t *testing.T, sock *net.UnixConn, count int, timeout time.Duration) []map[string]interface{} { + t.Helper() + sock.SetDeadline(time.Now().Add(timeout)) //nolint:errcheck + var records []map[string]interface{} + buf := make([]byte, 65536) + for len(records) < count { + n, err := sock.Read(buf) + if err != nil { + break + } + var m map[string]interface{} + if err := json.Unmarshal(buf[:n], &m); err != nil { + t.Logf("unmarshal: %v", err) + continue + } + records = append(records, m) + } + return records +} + +func TestParseAndSendH1Headers(t *testing.T) { + path := t.TempDir() + "/fp_h1.sock" + t.Setenv("DECNET_FP_SOCK", path) + // Reset the global socket so it reconnects to the test socket. + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { + done <- drainSock(t, srv, 1, 2*time.Second) + }() + + // Typical curl HTTP/1.1 request bytes. + raw := "GET /robots.txt HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: curl/8.0.1\r\n" + + "Accept: */*\r\n" + + "X-Custom-A: alpha\r\n" + + "X-Custom-B: beta\r\n" + + "\r\n" + + parseAndSendH1Headers("1.2.3.4:9999", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rec := records[0] + + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v, want http_request_headers", rec["kind"]) + } + if rec["proto_tag"] != "h1" { + t.Errorf("proto_tag: got %v, want h1", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v, want GET", rec["method"]) + } + if rec["path"] != "/robots.txt" { + t.Errorf("path: got %v, want /robots.txt", rec["path"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != 5 { + t.Fatalf("want 5 headers, got %d: %v", len(ordered), ordered) + } + // Wire order must be preserved exactly. + want := []string{"host", "user-agent", "accept", "x-custom-a", "x-custom-b"} + for i, pair := range ordered { + if pair[0] != want[i] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], want[i]) + } + } +} + +func TestParseAndSendH1Headers_StopsAtEmptyLine(t *testing.T) { + // Headers should not include body bytes after \r\n\r\n. + raw := "POST /login HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nuser=bob&pass=secret" + path := t.TempDir() + "/fp_h1b.sock" + t.Setenv("DECNET_FP_SOCK", path) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 2*time.Second) }() + parseAndSendH1Headers("10.0.0.1:1234", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rawOrdered, _ := json.Marshal(records[0]["headers_ordered"]) + var ordered [][]string + json.Unmarshal(rawOrdered, &ordered) //nolint:errcheck + if len(ordered) != 1 { + t.Fatalf("want 1 header (Content-Type), got %d: %v", len(ordered), ordered) + } + if ordered[0][0] != "content-type" { + t.Errorf("header[0]: got %q, want content-type", ordered[0][0]) + } +} diff --git a/decnet/templates/_caddy_modules/decnetfp/h2_test.go b/decnet/templates/_caddy_modules/decnetfp/h2_test.go new file mode 100644 index 00000000..f5c11551 --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/h2_test.go @@ -0,0 +1,206 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" + + "golang.org/x/net/http2/hpack" +) + +// buildH2Preface returns the 24-byte h2 client preface. +func buildH2Preface() []byte { + return []byte(h2ClientPreface) +} + +// buildH2Settings builds a SETTINGS frame payload from id/val pairs. +func buildH2Settings(pairs [][2]uint32) []byte { + payload := make([]byte, len(pairs)*6) + for i, p := range pairs { + binary.BigEndian.PutUint16(payload[i*6:], uint16(p[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], p[1]) + } + return buildH2Frame(0x4, 0, 0, payload) +} + +// buildH2Frame builds a raw h2 frame (9-byte header + payload). +func buildH2Frame(typ, flags byte, streamID uint32, payload []byte) []byte { + frame := make([]byte, 9+len(payload)) + frame[0] = byte(len(payload) >> 16) + frame[1] = byte(len(payload) >> 8) + frame[2] = byte(len(payload)) + frame[3] = typ + frame[4] = flags + binary.BigEndian.PutUint32(frame[5:], streamID) + copy(frame[9:], payload) + return frame +} + +// buildH2HeadersFrame encodes headers with HPACK and wraps in a HEADERS frame. +func buildH2HeadersFrame(streamID uint32, headers [][2]string, endHeaders bool) []byte { + var hbuf []byte + enc := hpack.NewEncoder(writeCloser{&hbuf}) + for _, h := range headers { + enc.WriteField(hpack.HeaderField{Name: h[0], Value: h[1]}) //nolint:errcheck + } + flags := byte(0) + if endHeaders { + flags |= 0x04 + } + return buildH2Frame(0x1, flags, streamID, hbuf) +} + +type writeCloser struct{ b *[]byte } + +func (w writeCloser) Write(p []byte) (int, error) { + *w.b = append(*w.b, p...) + return len(p), nil +} + +func TestParseH2HeadersLoop_DecodesHPACKOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, sockPath) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 3*time.Second) }() + + // Build: preface + SETTINGS + HEADERS (stream 1, END_HEADERS). + // Headers in a specific order to verify HPACK decode order is preserved. + wantHeaders := [][2]string{ + {":method", "GET"}, + {":path", "/index.html"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {"accept", "text/html"}, + {"user-agent", "Go-http-client/2.0"}, + {"accept-language", "en-US"}, + } + + preface := buildH2Preface() + settings := buildH2Settings([][2]uint32{{0x3, 100}}) // MAX_CONCURRENT_STREAMS=100 + headers := buildH2HeadersFrame(1, wantHeaders, true) + + tap := make(chan []byte, 256) + + // Feed the preface through the tap. + chunks := [][]byte{preface, settings, headers} + for _, c := range chunks { + cp := make([]byte, len(c)) + copy(cp, c) + tap <- cp + } + close(tap) + + go parseH2HeadersLoop("5.6.7.8:443", tap) + + records := <-done + if len(records) == 0 { + t.Fatal("no records received") + } + rec := records[0] + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v", rec["kind"]) + } + if rec["proto_tag"] != "h2" { + t.Errorf("proto_tag: got %v", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v", rec["method"]) + } + if rec["path"] != "/index.html" { + t.Errorf("path: got %v", rec["path"]) + } + if rec["accept_language"] != "en-US" { + t.Errorf("accept_language: got %v", rec["accept_language"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != len(wantHeaders) { + t.Fatalf("want %d headers, got %d: %v", len(wantHeaders), len(ordered), ordered) + } + for i, pair := range ordered { + if pair[0] != wantHeaders[i][0] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], wantHeaders[i][0]) + } + } +} + +func TestParseH2Settings_FrameOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2s.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + // SETTINGS frame with 3 params in a specific order. + settings := [][2]uint32{ + {0x4, 65535}, // INITIAL_WINDOW_SIZE + {0x3, 1000}, // MAX_CONCURRENT_STREAMS + {0x1, 65536}, // HEADER_TABLE_SIZE + } + payload := make([]byte, len(settings)*6) + for i, s := range settings { + binary.BigEndian.PutUint16(payload[i*6:], uint16(s[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], s[1]) + } + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + parseAndSendH2Settings("1.2.3.4:1234", payload) + + rec := <-done + if rec["kind"] != "h2_settings" { + t.Errorf("kind: got %v", rec["kind"]) + } + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []float64 // JSON numbers decode as float64 + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 3 { + t.Fatalf("want 3 frame_order entries, got %d", len(order)) + } + if order[0] != 4 || order[1] != 3 || order[2] != 1 { + t.Errorf("frame_order: got %v, want [4 3 1]", order) + } +} diff --git a/decnet/templates/_caddy_modules/decnetfp/h3_tracer_test.go b/decnet/templates/_caddy_modules/decnetfp/h3_tracer_test.go new file mode 100644 index 00000000..a28d5984 --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/h3_tracer_test.go @@ -0,0 +1,170 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" +) + +// encodeVarint encodes a uint64 as an RFC 9000 variable-length integer. +func encodeVarint(v uint64) []byte { + switch { + case v <= 0x3f: + return []byte{byte(v)} + case v <= 0x3fff: + return []byte{0x40 | byte(v>>8), byte(v)} + case v <= 0x3fffffff: + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(v)|0x80000000) + return b + default: + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, v|0xc000000000000000) + return b + } +} + +// buildH3ControlStream builds the opening bytes of an h3 control stream +// with a SETTINGS frame containing the given id/val pairs. +func buildH3ControlStream(settings [][2]uint64) []byte { + // Stream type = 0x00 (control stream) + var body []byte + for _, s := range settings { + body = append(body, encodeVarint(s[0])...) + body = append(body, encodeVarint(s[1])...) + } + // h3 frame: type=0x04 (SETTINGS), length=len(body), body + frame := append(encodeVarint(0x04), encodeVarint(uint64(len(body)))...) + frame = append(frame, body...) + + return append(encodeVarint(0x00), frame...) +} + +func TestTryParseH3ControlStream_ParsesSettings(t *testing.T) { + sockPath := t.TempDir() + "/fp_h3.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + settings := [][2]uint64{ + {0x01, 0}, // QPACK_MAX_TABLE_CAPACITY + {0x06, 0}, // MAX_FIELD_SECTION_SIZE (0 = unlimited) + {0x07, 0}, // QPACK_BLOCKED_STREAMS + {0x4242, 1}, // GREASE-like unknown value + } + data := buildH3ControlStream(settings) + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + tryParseH3ControlStream("9.8.7.6:443", data) + + rec := <-done + if rec == nil { + t.Fatal("no record received") + } + if rec["kind"] != "h3_settings" { + t.Errorf("kind: got %v, want h3_settings", rec["kind"]) + } + if rec["remote_addr"] != "9.8.7.6:443" { + t.Errorf("remote_addr: got %v", rec["remote_addr"]) + } + + rawSettings, _ := json.Marshal(rec["settings"]) + var gotSettings map[string]interface{} + json.Unmarshal(rawSettings, &gotSettings) //nolint:errcheck + if _, ok := gotSettings["QPACK_MAX_TABLE_CAPACITY"]; !ok { + t.Errorf("missing QPACK_MAX_TABLE_CAPACITY in settings: %v", gotSettings) + } + if _, ok := gotSettings["MAX_FIELD_SECTION_SIZE"]; !ok { + t.Errorf("missing MAX_FIELD_SECTION_SIZE in settings: %v", gotSettings) + } + + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []interface{} + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 4 { + t.Errorf("want 4 frame_order entries, got %d: %v", len(order), order) + } +} + +func TestTryParseH3ControlStream_WrongStreamType(t *testing.T) { + // Stream type 0x02 = QPACK encoder stream — should be ignored. + data := append(encodeVarint(0x02), []byte{0x00}...) + // Should not panic or emit any record. + tryParseH3ControlStream("1.2.3.4:443", data) // no socket — will silently drop +} + +func TestTryParseH3ControlStream_TruncatedData(t *testing.T) { + // Only stream-type prefix, no SETTINGS frame yet. + data := encodeVarint(0x00) + tryParseH3ControlStream("1.2.3.4:443", data) // must not panic +} + +func TestQuicVarint(t *testing.T) { + cases := []struct { + input []byte + want uint64 + wantN int + }{ + {[]byte{0x00}, 0, 1}, + {[]byte{0x3f}, 63, 1}, + {[]byte{0x40, 0x00}, 0, 2}, + {[]byte{0x7f, 0xff}, 16383, 2}, + {[]byte{0x80, 0x00, 0x00, 0x00}, 0, 4}, + {[]byte{0xbf, 0xff, 0xff, 0xff}, 1073741823, 4}, + // Empty input + {[]byte{}, 0, 0}, + // Truncated 2-byte + {[]byte{0x40}, 0, 0}, + } + for _, c := range cases { + v, n := quicVarint(c.input) + if v != c.want || n != c.wantN { + t.Errorf("quicVarint(%x) = (%d, %d), want (%d, %d)", c.input, v, n, c.want, c.wantN) + } + } +} + +func TestH3SettingName(t *testing.T) { + cases := []struct { + id uint64 + want string + }{ + {0x01, "QPACK_MAX_TABLE_CAPACITY"}, + {0x06, "MAX_FIELD_SECTION_SIZE"}, + {0x07, "QPACK_BLOCKED_STREAMS"}, + {0x08, "ENABLE_CONNECT_PROTOCOL"}, + {0x33, "H3_DATAGRAM"}, + {0x41, "UNKNOWN"}, // not a GREASE pattern (GREASE = 0x1f*N+0x21; 0x41-0x21=0x20, not div by 0x1f) + } + for _, c := range cases { + if got := h3SettingName(c.id); got != c.want { + t.Errorf("h3SettingName(0x%x) = %q, want %q", c.id, got, c.want) + } + } +} diff --git a/decnet/templates/_caddy_modules/decnetfp/h3app.go b/decnet/templates/_caddy_modules/decnetfp/h3app.go new file mode 100644 index 00000000..3e09b642 --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/h3app.go @@ -0,0 +1,178 @@ +package decnetfp + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "strings" + "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "go.uber.org/zap" +) + +func init() { + caddy.RegisterModule(H3App{}) + httpcaddyfile.RegisterGlobalOption("decnet_h3", parseH3AppOption) +} + +// parseH3AppOption maps the `decnet_h3` global Caddyfile block to the +// decnet_h3 app config (empty JSON — all config comes from env). +func parseH3AppOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { + for d.Next() { + for d.NextBlock(0) { + } + } + return json.RawMessage(`{}`), nil +} + +// H3App is a Caddy app that owns the QUIC/UDP listener on port 443, forwarding +// accepted h3 connections to the Caddy HTTP app's handler chain. This is the +// only way to inject a per-connection quic-go Tracer for h3 SETTINGS capture, +// since Caddy does not expose its QUIC config. +// +// Activate with a `decnet_h3` global Caddyfile block AND omit `h3` from the +// `:443` server's `protocols` list, otherwise both this app and Caddy's HTTP +// server will fight over UDP/443. +// +// The app is a no-op when `HTTP_VERSIONS` env does not contain `"http/3"`. +type H3App struct { + caddyCtx caddy.Context + logger *zap.Logger + listener *quic.Listener + transport *quic.Transport + httpSrv *http3.Server + cancelLoop context.CancelFunc + wg *sync.WaitGroup +} + +func (H3App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "decnet_h3", + New: func() caddy.Module { return new(H3App) }, + } +} + +func (a *H3App) Provision(ctx caddy.Context) error { + a.caddyCtx = ctx + a.logger = ctx.Logger() + return nil +} + +func (a *H3App) Start() error { + a.wg = &sync.WaitGroup{} + if !strings.Contains(os.Getenv("HTTP_VERSIONS"), "http/3") { + return nil + } + + cert, err := tls.LoadX509KeyPair("/opt/tls/cert.pem", "/opt/tls/key.pem") + if err != nil { + return fmt.Errorf("decnet_h3: load TLS cert: %w", err) + } + tlsCfg := &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"h3"}, + } + + udpConn, err := net.ListenPacket("udp", ":443") + if err != nil { + return fmt.Errorf("decnet_h3: bind UDP/443: %w", err) + } + tr := &quic.Transport{Conn: udpConn.(*net.UDPConn)} + a.transport = tr + + ln, err := tr.Listen(tlsCfg, &quic.Config{Tracer: newH3SettingsTracer}) + if err != nil { + tr.Close() + return fmt.Errorf("decnet_h3: quic listen: %w", err) + } + a.listener = ln + + handler, err := a.findHTTPHandler() + if err != nil { + ln.Close() + tr.Close() + return fmt.Errorf("decnet_h3: find HTTP handler: %w", err) + } + + a.httpSrv = &http3.Server{Handler: handler} + + loopCtx, cancel := context.WithCancel(context.Background()) + a.cancelLoop = cancel + a.wg.Add(1) + go func() { + defer a.wg.Done() + a.acceptLoop(loopCtx) + }() + + a.logger.Info("decnet_h3 listening on UDP/443") + return nil +} + +func (a *H3App) acceptLoop(ctx context.Context) { + for { + conn, err := a.listener.Accept(ctx) + if err != nil { + return + } + wrapped := &h3SettingsTappingConn{ + Connection: conn, + remoteAddr: conn.RemoteAddr().String(), + } + go func() { + a.httpSrv.ServeQUICConn(wrapped) //nolint:errcheck + }() + } +} + +func (a *H3App) Stop() error { + if a.cancelLoop != nil { + a.cancelLoop() + } + if a.listener != nil { + a.listener.Close() + } + a.wg.Wait() + if a.transport != nil { + a.transport.Close() + } + return nil +} + +// findHTTPHandler returns the http.Handler for Caddy's :443 server. +func (a *H3App) findHTTPHandler() (http.Handler, error) { + appIface, err := a.caddyCtx.App("http") + if err != nil { + return nil, fmt.Errorf("get http app: %w", err) + } + httpApp, ok := appIface.(*caddyhttp.App) + if !ok { + return nil, fmt.Errorf("unexpected http app type %T", appIface) + } + for _, srv := range httpApp.Servers { + for _, addr := range srv.Listen { + if strings.Contains(addr, ":443") { + return srv, nil + } + } + } + // Fall back to any available server. + for _, srv := range httpApp.Servers { + return srv, nil + } + return nil, fmt.Errorf("no HTTP servers found in caddy http app") +} + +var ( + _ caddy.App = (*H3App)(nil) + _ caddy.Provisioner = (*H3App)(nil) +) diff --git a/decnet/templates/_caddy_modules/decnetfp/h3conn.go b/decnet/templates/_caddy_modules/decnetfp/h3conn.go new file mode 100644 index 00000000..ccf3f82b --- /dev/null +++ b/decnet/templates/_caddy_modules/decnetfp/h3conn.go @@ -0,0 +1,184 @@ +package decnetfp + +import ( + "bytes" + "context" + "io" + "sync" + "time" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" +) + +// newH3SettingsTracer is the quic.Config.Tracer factory. We don't use +// quic-go's logging.ConnectionTracer for SETTINGS (its ReceivedStreamFrame +// hook gives only metadata, not payload bytes). The actual h3 SETTINGS +// capture happens in h3TappingUniStream by wrapping AcceptUniStream. +// This function returns nil (no-op tracer) so quic-go uses its default path. +func newH3SettingsTracer(_ context.Context, _ logging.Perspective, _ quic.ConnectionID) *logging.ConnectionTracer { + return nil +} + +// ── QUIC connection wrapper ─────────────────────────────────────────────────── + +// h3SettingsTappingConn wraps quic.Connection and intercepts AcceptUniStream +// so the first bytes of each client-initiated unidirectional stream can be +// inspected for h3 control stream SETTINGS before being replayed to the +// http3.Server. +type h3SettingsTappingConn struct { + quic.Connection + remoteAddr string +} + +func (c *h3SettingsTappingConn) AcceptUniStream(ctx context.Context) (quic.ReceiveStream, error) { + stream, err := c.Connection.AcceptUniStream(ctx) + if err != nil { + return stream, err + } + return &h3TappingUniStream{ReceiveStream: stream, remoteAddr: c.remoteAddr}, nil +} + +// ── QUIC receive-stream wrapper ─────────────────────────────────────────────── + +// h3TappingUniStream peeks at the first bytes of a unidirectional stream to +// identify the h3 control stream (stream type 0x00, RFC 9114 §6.2.1) and +// extract its first SETTINGS frame, then replays all bytes to the caller. +type h3TappingUniStream struct { + quic.ReceiveStream + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// maxH3ControlPeek is enough to cover the stream-type varint + SETTINGS +// frame type varint + frame-length varint + a typical SETTINGS frame body +// (6 settings × 8 bytes each = 48 bytes, plus 3 varint headers ≈ 64 bytes). +const maxH3ControlPeek = 256 + +func (s *h3TappingUniStream) Read(p []byte) (int, error) { + s.once.Do(func() { + scratch := make([]byte, maxH3ControlPeek) + n, _ := s.ReceiveStream.Read(scratch) + s.buf.Write(scratch[:n]) + go tryParseH3ControlStream(s.remoteAddr, s.buf.Bytes()) + s.reader = io.MultiReader(&s.buf, s.ReceiveStream) + }) + if s.reader != nil { + return s.reader.Read(p) + } + return s.ReceiveStream.Read(p) +} + +// tryParseH3ControlStream examines the peeked bytes. If the stream opens +// with stream-type 0x00 (h3 control stream) and the first frame is SETTINGS +// (type 0x04), it emits an h3_settings fp record. All errors are silent — +// this is a best-effort tap. +func tryParseH3ControlStream(remoteAddr string, data []byte) { + streamType, c0 := quicVarint(data) + if c0 == 0 || streamType != 0x00 { + return // not the h3 control stream + } + data = data[c0:] + + frameType, c1 := quicVarint(data) + if c1 == 0 { + return + } + data = data[c1:] + + frameLen, c2 := quicVarint(data) + if c2 == 0 { + return + } + data = data[c2:] + + // Per RFC 9114 §7.2.4: the first frame on the control stream MUST be SETTINGS. + if frameType != 0x04 { + return + } + if uint64(len(data)) < frameLen { + return // need more bytes — we only peeked 256 + } + body := data[:frameLen] + + settings := make(map[string]uint64) + frameOrder := make([]uint64, 0, 8) + for len(body) > 0 { + id, ci := quicVarint(body) + if ci == 0 { + break + } + body = body[ci:] + val, cv := quicVarint(body) + if cv == 0 { + break + } + body = body[cv:] + settings[h3SettingName(id)] = val + frameOrder = append(frameOrder, id) + } + + sendFP(map[string]interface{}{ + "kind": "h3_settings", + "remote_addr": remoteAddr, + "settings": settings, + "frame_order": frameOrder, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// quicVarint decodes an RFC 9000 §16 variable-length integer. +// Returns (value, bytes_consumed); bytes_consumed == 0 on failure. +func quicVarint(b []byte) (uint64, int) { + if len(b) == 0 { + return 0, 0 + } + prefix := b[0] >> 6 + switch prefix { + case 0: + return uint64(b[0] & 0x3f), 1 + case 1: + if len(b) < 2 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<8 | uint64(b[1]), 2 + case 2: + if len(b) < 4 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 + case 3: + if len(b) < 8 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | + uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 + } + return 0, 0 +} + +// h3SettingName maps RFC 9114 and extension SETTINGS IDs to human-readable names. +func h3SettingName(id uint64) string { + switch id { + case 0x01: + return "QPACK_MAX_TABLE_CAPACITY" + case 0x06: + return "MAX_FIELD_SECTION_SIZE" + case 0x07: + return "QPACK_BLOCKED_STREAMS" + case 0x08: + return "ENABLE_CONNECT_PROTOCOL" + case 0x33: + return "H3_DATAGRAM" + case 0xc671706a: + return "ENABLE_WEBTRANSPORT" + default: + // GREASE values per RFC 9114 §7.2.8 pattern (0x1f * N + 0x21) + if id > 0x20 && (id-0x21)%0x1f == 0 { + return "GREASE" + } + return "UNKNOWN" + } +} diff --git a/decnet/templates/_caddy_modules/decnetfp/module.go b/decnet/templates/_caddy_modules/decnetfp/module.go index b8febca5..4ebdce99 100644 --- a/decnet/templates/_caddy_modules/decnetfp/module.go +++ b/decnet/templates/_caddy_modules/decnetfp/module.go @@ -1,21 +1,17 @@ -// Package decnetfp provides three Caddy modules for HTTP fingerprint capture. +// Package decnetfp provides Caddy modules for HTTP fingerprint capture. // // Registered modules: -// - caddy.listeners.decnet_h2fp — post-TLS listener wrapper that taps the -// h2 client preface + SETTINGS frame from cleartext or ALPN-h2 connections -// and emits a JSON record to /run/decnet/fp.sock (unix datagram). -// - http.handlers.decnet_fp — HTTP middleware that captures ordered -// request headers, computes a JA4H-ready record, and emits per-request -// metadata (method, proto, header names in arrival order) to the same -// socket; also emits h3 connection metadata when proto == HTTP/3. -// - caddy.logging.encoders.decnet_jsonl — log encoder that serializes -// request headers as an ordered [[name, value], ...] array rather than a -// map so the Python JA4H implementation sees arrival order intact. +// - caddy.listeners.decnet_fp — post-TLS listener wrapper that taps +// the h2 client preface (SETTINGS + HEADERS frames via persistent HPACK +// decoder) and h1 request-line / header bytes, emitting ordered header +// name lists to /run/decnet/fp.sock (unix datagram). +// - http.handlers.decnet_fp — HTTP middleware that emits an +// access_log record (status code, bytes, protocol) after each response. +// - caddy.logging.encoders.decnet_jsonl — log encoder stub (registered +// but not wired into Caddyfile; access_log comes from the handler above). // -// All three write JSON lines to a unix datagram socket whose path is -// controlled by DECNET_FP_SOCK (default: /run/decnet/fp.sock). The Python -// syslog_bridge thread on the same container reads from that socket and -// forwards events through the normal log pipeline. +// All modules write JSON lines to a unix datagram socket whose path is +// controlled by DECNET_FP_SOCK (default: /run/decnet/fp.sock). package decnetfp import ( @@ -35,10 +31,11 @@ import ( "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "go.uber.org/zap" + "golang.org/x/net/http2/hpack" ) func init() { - caddy.RegisterModule(H2FPListenerWrapper{}) + caddy.RegisterModule(FPListenerWrapper{}) caddy.RegisterModule(FPHandler{}) caddy.RegisterModule(DecnetJSONLEncoder{}) httpcaddyfile.RegisterHandlerDirective("decnet_fp", parseFPHandler) @@ -81,61 +78,207 @@ func sendFP(record map[string]interface{}) { sockConn.Write(b) //nolint:errcheck } -// ── caddy.listeners.decnet_h2fp ─────────────────────────────────────────────── +// ── caddy.listeners.decnet_fp ───────────────────────────────────────────────── -// H2FPListenerWrapper is a post-TLS Caddy listener wrapper that taps the h2 -// client preface + SETTINGS frame. Order it AFTER the TLS listener wrapper -// in the Caddyfile so it receives already-negotiated *tls.Conn connections. +// FPListenerWrapper is a post-TLS Caddy listener wrapper that: +// - For h2 ALPN connections: taps the h2 client preface (SETTINGS frame) +// and then continuously parses HEADERS/CONTINUATION frames via a +// persistent HPACK decoder, emitting ordered header name lists. +// - For h1 ALPN connections (or plain connections): taps the first request's +// header bytes, emitting ordered header names from the wire. +// +// Place it AFTER the TLS listener wrapper so it sees post-TLS data: // // listener_wrappers { // tls -// decnet_h2fp +// decnet_fp // } -type H2FPListenerWrapper struct { +type FPListenerWrapper struct { logger *zap.Logger } -func (H2FPListenerWrapper) CaddyModule() caddy.ModuleInfo { +func (FPListenerWrapper) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ - ID: "caddy.listeners.decnet_h2fp", - New: func() caddy.Module { return new(H2FPListenerWrapper) }, + ID: "caddy.listeners.decnet_fp", + New: func() caddy.Module { return new(FPListenerWrapper) }, } } -func (w *H2FPListenerWrapper) Provision(ctx caddy.Context) error { +func (w *FPListenerWrapper) Provision(ctx caddy.Context) error { w.logger = ctx.Logger() return nil } -func (w *H2FPListenerWrapper) WrapListener(ln net.Listener) net.Listener { - return &h2FPListener{Listener: ln, logger: w.logger} +func (w *FPListenerWrapper) WrapListener(ln net.Listener) net.Listener { + return &fpListener{Listener: ln, logger: w.logger} } -func (w *H2FPListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { +func (w *FPListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } -type h2FPListener struct { +type fpListener struct { net.Listener logger *zap.Logger } -func (l *h2FPListener) Accept() (net.Conn, error) { +func (l *fpListener) Accept() (net.Conn, error) { conn, err := l.Listener.Accept() if err != nil { return conn, err } + remote := conn.RemoteAddr().String() + tlsConn, ok := conn.(*tls.Conn) if !ok { - return conn, nil + // Plain (cleartext) connection — peek to distinguish h2c from h1. + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil } state := tlsConn.ConnectionState() - if state.NegotiatedProtocol != "h2" { - return conn, nil + switch state.NegotiatedProtocol { + case "h2": + return &h2TappingConn{Conn: conn, remoteAddr: remote}, nil + default: + // http/1.1 ALPN or no ALPN — post-TLS plaintext is h1; safe to + // use plainTappingConn (h2c preface won't appear after TLS). + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil } - return &h2TappingConn{Conn: conn, remoteAddr: conn.RemoteAddr().String()}, nil } +// ── Plain connection tap (h1 and h2c) ──────────────────────────────────────── + +const h1MaxHeaderBuf = 8192 + +// plainTappingConn handles post-accept connections that are not TLS h2. +// It peeks the first bytes to distinguish h2c prior-knowledge from h1, +// then routes to the appropriate parser. Used for both cleartext (port 80) +// and TLS-h1 (ALPN "" or "http/1.1") connections. +type plainTappingConn struct { + net.Conn + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// tapWriter is a non-blocking io.Writer that drops if the channel is full. +type tapWriter struct { + ch chan<- []byte +} + +func (t tapWriter) Write(p []byte) (int, error) { + cp := make([]byte, len(p)) + copy(cp, p) + select { + case t.ch <- cp: + default: + } + return len(p), nil +} + +func (c *plainTappingConn) Read(b []byte) (int, error) { + c.once.Do(func() { + // Peek exactly len(h2ClientPreface) bytes to detect h2c prior knowledge. + preface := make([]byte, len(h2ClientPreface)) + n, _ := io.ReadFull(c.Conn, preface) + c.buf.Write(preface[:n]) + + if n == len(h2ClientPreface) && string(preface) == h2ClientPreface { + // h2c prior-knowledge connection — run the same SETTINGS+HPACK tap. + hdr9 := make([]byte, 9) + if m, err := io.ReadFull(c.Conn, hdr9); m == 9 && err == nil { + c.buf.Write(hdr9) + frameLen := int(hdr9[0])<<16 | int(hdr9[1])<<8 | int(hdr9[2]) + frameType := hdr9[3] + if frameType == 0x4 && frameLen > 0 && frameLen <= 16384 { + payload := make([]byte, frameLen) + if _, err := io.ReadFull(c.Conn, payload); err == nil { + c.buf.Write(payload) + go parseAndSendH2Settings(c.remoteAddr, payload) + } + } + } + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) + return + } + + // h1 — buffer up to h1MaxHeaderBuf or until \r\n\r\n. + scratch := make([]byte, h1MaxHeaderBuf) + for c.buf.Len() < h1MaxHeaderBuf { + nn, err := c.Conn.Read(scratch[:h1MaxHeaderBuf-c.buf.Len()]) + c.buf.Write(scratch[:nn]) + if bytes.Contains(c.buf.Bytes(), []byte("\r\n\r\n")) { + break + } + if err != nil { + break + } + } + go parseAndSendH1Headers(c.remoteAddr, c.buf.Bytes()) + c.reader = io.MultiReader(&c.buf, c.Conn) + }) + if c.reader == nil { + return c.Conn.Read(b) + } + return c.reader.Read(b) +} + +func parseAndSendH1Headers(remoteAddr string, raw []byte) { + idx := bytes.Index(raw, []byte("\r\n\r\n")) + if idx < 0 { + idx = len(raw) + } + lines := bytes.Split(raw[:idx], []byte("\r\n")) + if len(lines) == 0 { + return + } + // First line: "GET /path HTTP/1.1" + requestLine := string(lines[0]) + var method, path, proto string + parts := bytes.Fields(lines[0]) + if len(parts) >= 3 { + method = string(parts[0]) + path = string(parts[1]) + proto = string(parts[2]) + } + + var ordered [][]string // [[name, value], ...] + var cookie, acceptLang string + for _, line := range lines[1:] { + sep := bytes.IndexByte(line, ':') + if sep < 0 { + continue + } + name := string(bytes.ToLower(bytes.TrimSpace(line[:sep]))) + value := string(bytes.TrimSpace(line[sep+1:])) + ordered = append(ordered, []string{name, value}) + switch name { + case "cookie": + cookie = value + case "accept-language": + acceptLang = value + } + } + + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "proto_tag": "h1", + "request_line": requestLine, + "method": method, + "path": path, + "proto": proto, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// ── H2 client preface + HPACK continuous tap ────────────────────────────────── + const h2ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" type h2TappingConn struct { @@ -148,10 +291,10 @@ type h2TappingConn struct { func (c *h2TappingConn) Read(b []byte) (int, error) { c.once.Do(func() { - // Buffer the h2 client preface (24 bytes) + first frame header (9 bytes). + // Buffer client preface (24 B) + first frame header (9 B). hdr := make([]byte, len(h2ClientPreface)+9) if _, err := io.ReadFull(c.Conn, hdr); err != nil { - c.buf.Write(hdr) // replay what we got even on partial read + c.buf.Write(hdr) c.reader = io.MultiReader(&c.buf, c.Conn) return } @@ -169,7 +312,11 @@ func (c *h2TappingConn) Read(b []byte) (int, error) { go parseAndSendH2Settings(c.remoteAddr, payload) } } - c.reader = io.MultiReader(&c.buf, c.Conn) + + // Start HPACK continuous parse on a non-blocking channel tap. + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) }) if c.reader == nil { return c.Conn.Read(b) @@ -219,13 +366,151 @@ func settingName(id uint16) string { } } +// hpackConn holds per-connection HPACK decoder state. +type hpackConn struct { + decoder *hpack.Decoder + currentFields []hpack.HeaderField +} + +func newHpackConn(maxTableSize uint32) *hpackConn { + hc := &hpackConn{} + hc.decoder = hpack.NewDecoder(maxTableSize, func(f hpack.HeaderField) { + hc.currentFields = append(hc.currentFields, f) + }) + return hc +} + +// decode decodes a header block fragment, appending to existing and returning combined. +func (hc *hpackConn) decode(fragment []byte, existing []hpack.HeaderField) []hpack.HeaderField { + hc.currentFields = existing + hc.decoder.Write(fragment) //nolint:errcheck + return hc.currentFields +} + +func parseH2HeadersLoop(remoteAddr string, tap <-chan []byte) { + var buf []byte + prefaceLen := len(h2ClientPreface) + prefaceSkipped := false + hc := newHpackConn(4096) + + type streamState struct { + fields []hpack.HeaderField + } + streams := make(map[uint32]*streamState) + + for chunk := range tap { + buf = append(buf, chunk...) + + if !prefaceSkipped { + if len(buf) < prefaceLen { + continue + } + buf = buf[prefaceLen:] + prefaceSkipped = true + } + + for len(buf) >= 9 { + frameLen := int(buf[0])<<16 | int(buf[1])<<8 | int(buf[2]) + frameType := buf[3] + flags := buf[4] + streamID := binary.BigEndian.Uint32(buf[5:9]) & 0x7fffffff + + total := 9 + frameLen + if len(buf) < total { + break + } + payload := buf[9:total] + buf = buf[total:] + + switch frameType { + case 0x4: // SETTINGS from client + if flags&0x1 != 0 { // ACK + break + } + for i := 0; i+6 <= len(payload); i += 6 { + id := binary.BigEndian.Uint16(payload[i : i+2]) + val := binary.BigEndian.Uint32(payload[i+2 : i+6]) + if id == 0x1 { // SETTINGS_HEADER_TABLE_SIZE + hc.decoder.SetMaxDynamicTableSize(val) + } + } + + case 0x1, 0x9: // HEADERS, CONTINUATION + fragment := payload + if frameType == 0x1 { + off := 0 + var padLen byte + if flags&0x08 != 0 { // PADDED + if len(fragment) < 1 { + continue + } + padLen = fragment[0] + off = 1 + } + if flags&0x20 != 0 { // PRIORITY + off += 5 + } + end := len(fragment) - int(padLen) + if off > end || end < 0 { + continue + } + fragment = fragment[off:end] + } + + ss, ok := streams[streamID] + if !ok { + ss = &streamState{} + streams[streamID] = ss + } + ss.fields = hc.decode(fragment, ss.fields) + + if flags&0x04 != 0 { // END_HEADERS + emitH2RequestHeaders(remoteAddr, streamID, ss.fields) + delete(streams, streamID) + } + } + } + } +} + +func emitH2RequestHeaders(remoteAddr string, streamID uint32, fields []hpack.HeaderField) { + ordered := make([][]string, 0, len(fields)) + var cookie, acceptLang, method, path string + for _, f := range fields { + ordered = append(ordered, []string{f.Name, f.Value}) + switch f.Name { + case "cookie": + cookie = f.Value + case "accept-language": + acceptLang = f.Value + case ":method": + method = f.Value + case ":path": + path = f.Value + } + } + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "stream_id": streamID, + "proto_tag": "h2", + "method": method, + "path": path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + // ── http.handlers.decnet_fp ─────────────────────────────────────────────────── -// FPHandler is an HTTP middleware that captures per-request fingerprint data: -// - Ordered header name list (for JA4H computation in Python) -// - Protocol version (h1 / h2 / h3) -// - Cookie and Accept-Language values (JA4H inputs) -// - For h3 requests: QUIC connection metadata (best-effort) +// FPHandler is HTTP middleware that emits an access_log record (status code, +// bytes, proto) after each response via the fp socket. For h3 requests it +// also emits a best-effort http_request_headers record (header order degraded +// — QPACK decode order is preserved by quic-go but the Go map randomises it +// further; canonical h3 header order requires request-stream QPACK tapping +// which is a follow-up task). type FPHandler struct { logger *zap.Logger } @@ -246,24 +531,31 @@ func (h *FPHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } -func (h *FPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - // Collect ordered header names. Go's http.Header is a map so we cannot - // recover arrival order from it directly. We read the raw wire order via - // the request's trailer mechanism... except that's also a map. - // - // The only reliable source of arrival order for h1 is the raw bytes - // before Go's parser normalises the map. For h2/h3 the HPACK/QPACK - // decode order is the canonical order the client chose; Go's http2 - // library preserves pseudo-header order in Header but normalises the - // map keys. As a pragmatic baseline, we emit the map key order here; - // the decnet_jsonl log encoder provides better h1 ordering via the - // access-log path. - ordered := make([]string, 0, len(r.Header)) - for name := range r.Header { - ordered = append(ordered, name) - } +// responseCapture captures the status code and bytes written. +type responseCapture struct { + http.ResponseWriter + status int + bytes int +} + +func (rc *responseCapture) WriteHeader(status int) { + rc.status = status + rc.ResponseWriter.WriteHeader(status) +} + +func (rc *responseCapture) Write(b []byte) (int, error) { + n, err := rc.ResponseWriter.Write(b) + rc.bytes += n + if rc.status == 0 { + rc.status = 200 + } + return n, err +} + +func (h *FPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + rc := &responseCapture{ResponseWriter: w} + err := next.ServeHTTP(rc, r) - proto := r.Proto protoTag := "h1" if r.ProtoMajor == 2 { protoTag = "h2" @@ -271,33 +563,61 @@ func (h *FPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy protoTag = "h3" } - record := map[string]interface{}{ - "kind": "http_request", - "remote_addr": r.RemoteAddr, - "method": r.Method, - "path": r.URL.Path, - "proto": proto, - "proto_tag": protoTag, - "headers_ordered": ordered, - "cookie": r.Header.Get("Cookie"), - "accept_language": r.Header.Get("Accept-Language"), - "ts": time.Now().UTC().Format(time.RFC3339), + status := rc.status + if status == 0 { + status = 200 } + go sendFP(map[string]interface{}{ + "kind": "access_log", + "remote_addr": r.RemoteAddr, + "method": r.Method, + "path": r.URL.Path, + "proto": r.Proto, + "proto_tag": protoTag, + "status": status, + "bytes": rc.bytes, + "ts": time.Now().UTC().Format(time.RFC3339), + }) + + // For h3, emit best-effort http_request_headers (map order, degraded). if r.ProtoMajor == 3 { - // Emit h3 metadata. Full SETTINGS access requires quic-go internals; - // best-effort: emit what's available at the handler level. - record["h3_note"] = "settings_not_available_from_handler" + ordered := make([][]string, 0, len(r.Header)) + var cookie, acceptLang string + for name, vals := range r.Header { + v := "" + if len(vals) > 0 { + v = vals[0] + } + ordered = append(ordered, []string{name, v}) + switch http.CanonicalHeaderKey(name) { + case "Cookie": + cookie = v + case "Accept-Language": + acceptLang = v + } + } + go sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": r.RemoteAddr, + "proto_tag": "h3", + "method": r.Method, + "path": r.URL.Path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "h3_order_note": "degraded_map_iteration", + "ts": time.Now().UTC().Format(time.RFC3339), + }) } - go sendFP(record) - return next.ServeHTTP(w, r) + return err } var ( - _ caddy.Provisioner = (*H2FPListenerWrapper)(nil) - _ caddy.ListenerWrapper = (*H2FPListenerWrapper)(nil) - _ caddyfile.Unmarshaler = (*H2FPListenerWrapper)(nil) + _ caddy.Provisioner = (*FPListenerWrapper)(nil) + _ caddy.ListenerWrapper = (*FPListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*FPListenerWrapper)(nil) _ caddy.Provisioner = (*FPHandler)(nil) _ caddyhttp.MiddlewareHandler = (*FPHandler)(nil) _ caddyfile.Unmarshaler = (*FPHandler)(nil) @@ -305,22 +625,9 @@ var ( // ── caddy.logging.encoders.decnet_jsonl ────────────────────────────────────── -// DecnetJSONLEncoder is a Caddy access-log encoder that emits JSON with -// request headers as an ordered [[name, value], ...] array. For h1 -// connections, Go's HTTP/1.1 parser preserves the raw order in -// `req.Header` via the hidden `req.Header["_order_"]` scratch space used -// by x/net/http2. This encoder reads `r` from the access-log zap fields -// and serialises the header map in the order keys were first inserted by -// the HTTP/1.1 parser (which iterates in wire order for h1). -// -// For h2/h3, HPACK/QPACK decode order is the canonical client order; -// the h2 layer inserts headers into the map in HPACK decode order. -// -// NOTE: This is a best-effort implementation. Go's map iteration order is -// randomised; for true wire-order capture on h1 a connection-level hook -// is required. The listener wrapper (caddy.listeners.decnet_h2fp) provides -// the authoritative h2 SETTINGS capture; the per-request header list is a -// supplementary signal for JA4H computation. +// DecnetJSONLEncoder is a registered Caddy module stub. A full zapcore.Encoder +// implementation (required for `log { format decnet_jsonl }`) is deferred; +// access_log records are emitted by FPHandler.ServeHTTP instead. type DecnetJSONLEncoder struct { logger *zap.Logger } @@ -336,15 +643,3 @@ func (e *DecnetJSONLEncoder) Provision(ctx caddy.Context) error { e.logger = ctx.Logger() return nil } - -func (e *DecnetJSONLEncoder) Encode(fields []zap.Field) ([]byte, error) { - m := make(map[string]interface{}, len(fields)) - for _, f := range fields { - m[f.Key] = f.Interface - } - b, err := json.Marshal(m) - if err != nil { - return nil, err - } - return append(b, '\n'), nil -} diff --git a/decnet/templates/http/_caddy_modules/decnetfp/go.mod b/decnet/templates/http/_caddy_modules/decnetfp/go.mod new file mode 100644 index 00000000..1759f457 --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/go.mod @@ -0,0 +1,116 @@ +module github.com/decnet/caddy-fp + +go 1.22 + +require ( + github.com/caddyserver/caddy/v2 v2.8.4 + github.com/quic-go/quic-go v0.44.0 + go.uber.org/zap v1.27.0 + golang.org/x/net v0.27.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/certmagic v0.21.3 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/cel-go v0.20.1 // indirect + github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/libdns/libdns v0.2.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mholt/acmez/v2 v2.0.1 // indirect + github.com/miekg/dns v1.1.59 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/slackhq/nebula v1.6.1 // indirect + github.com/smallstep/certificates v0.26.1 // indirect + github.com/smallstep/nosql v0.6.1 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/smallstep/truststore v0.13.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect + github.com/urfave/cli v1.22.14 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 // indirect + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/mock v0.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.2.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect +) diff --git a/decnet/templates/http/_caddy_modules/decnetfp/go.sum b/decnet/templates/http/_caddy_modules/decnetfp/go.sum new file mode 100644 index 00000000..c902cb6b --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/go.sum @@ -0,0 +1,592 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk= +github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw= +github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= +github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= +github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= +github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= +github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/decnet/templates/http/_caddy_modules/decnetfp/h1_test.go b/decnet/templates/http/_caddy_modules/decnetfp/h1_test.go new file mode 100644 index 00000000..7d8e5ecf --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/h1_test.go @@ -0,0 +1,150 @@ +package decnetfp + +import ( + "encoding/json" + "net" + "testing" + "time" +) + +// bindSock creates a unix datagram socket at path and returns it. +// The caller must close it (or register a t.Cleanup). +func bindSock(t *testing.T, path string) *net.UnixConn { + t.Helper() + sock, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: path, Net: "unixgram"}) + if err != nil { + t.Fatalf("listen %s: %v", path, err) + } + return sock +} + +// drainSock reads up to n records from an already-bound unix datagram socket. +func drainSock(t *testing.T, sock *net.UnixConn, count int, timeout time.Duration) []map[string]interface{} { + t.Helper() + sock.SetDeadline(time.Now().Add(timeout)) //nolint:errcheck + var records []map[string]interface{} + buf := make([]byte, 65536) + for len(records) < count { + n, err := sock.Read(buf) + if err != nil { + break + } + var m map[string]interface{} + if err := json.Unmarshal(buf[:n], &m); err != nil { + t.Logf("unmarshal: %v", err) + continue + } + records = append(records, m) + } + return records +} + +func TestParseAndSendH1Headers(t *testing.T) { + path := t.TempDir() + "/fp_h1.sock" + t.Setenv("DECNET_FP_SOCK", path) + // Reset the global socket so it reconnects to the test socket. + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { + done <- drainSock(t, srv, 1, 2*time.Second) + }() + + // Typical curl HTTP/1.1 request bytes. + raw := "GET /robots.txt HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: curl/8.0.1\r\n" + + "Accept: */*\r\n" + + "X-Custom-A: alpha\r\n" + + "X-Custom-B: beta\r\n" + + "\r\n" + + parseAndSendH1Headers("1.2.3.4:9999", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rec := records[0] + + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v, want http_request_headers", rec["kind"]) + } + if rec["proto_tag"] != "h1" { + t.Errorf("proto_tag: got %v, want h1", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v, want GET", rec["method"]) + } + if rec["path"] != "/robots.txt" { + t.Errorf("path: got %v, want /robots.txt", rec["path"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != 5 { + t.Fatalf("want 5 headers, got %d: %v", len(ordered), ordered) + } + // Wire order must be preserved exactly. + want := []string{"host", "user-agent", "accept", "x-custom-a", "x-custom-b"} + for i, pair := range ordered { + if pair[0] != want[i] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], want[i]) + } + } +} + +func TestParseAndSendH1Headers_StopsAtEmptyLine(t *testing.T) { + // Headers should not include body bytes after \r\n\r\n. + raw := "POST /login HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nuser=bob&pass=secret" + path := t.TempDir() + "/fp_h1b.sock" + t.Setenv("DECNET_FP_SOCK", path) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 2*time.Second) }() + parseAndSendH1Headers("10.0.0.1:1234", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rawOrdered, _ := json.Marshal(records[0]["headers_ordered"]) + var ordered [][]string + json.Unmarshal(rawOrdered, &ordered) //nolint:errcheck + if len(ordered) != 1 { + t.Fatalf("want 1 header (Content-Type), got %d: %v", len(ordered), ordered) + } + if ordered[0][0] != "content-type" { + t.Errorf("header[0]: got %q, want content-type", ordered[0][0]) + } +} diff --git a/decnet/templates/http/_caddy_modules/decnetfp/h2_test.go b/decnet/templates/http/_caddy_modules/decnetfp/h2_test.go new file mode 100644 index 00000000..f5c11551 --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/h2_test.go @@ -0,0 +1,206 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" + + "golang.org/x/net/http2/hpack" +) + +// buildH2Preface returns the 24-byte h2 client preface. +func buildH2Preface() []byte { + return []byte(h2ClientPreface) +} + +// buildH2Settings builds a SETTINGS frame payload from id/val pairs. +func buildH2Settings(pairs [][2]uint32) []byte { + payload := make([]byte, len(pairs)*6) + for i, p := range pairs { + binary.BigEndian.PutUint16(payload[i*6:], uint16(p[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], p[1]) + } + return buildH2Frame(0x4, 0, 0, payload) +} + +// buildH2Frame builds a raw h2 frame (9-byte header + payload). +func buildH2Frame(typ, flags byte, streamID uint32, payload []byte) []byte { + frame := make([]byte, 9+len(payload)) + frame[0] = byte(len(payload) >> 16) + frame[1] = byte(len(payload) >> 8) + frame[2] = byte(len(payload)) + frame[3] = typ + frame[4] = flags + binary.BigEndian.PutUint32(frame[5:], streamID) + copy(frame[9:], payload) + return frame +} + +// buildH2HeadersFrame encodes headers with HPACK and wraps in a HEADERS frame. +func buildH2HeadersFrame(streamID uint32, headers [][2]string, endHeaders bool) []byte { + var hbuf []byte + enc := hpack.NewEncoder(writeCloser{&hbuf}) + for _, h := range headers { + enc.WriteField(hpack.HeaderField{Name: h[0], Value: h[1]}) //nolint:errcheck + } + flags := byte(0) + if endHeaders { + flags |= 0x04 + } + return buildH2Frame(0x1, flags, streamID, hbuf) +} + +type writeCloser struct{ b *[]byte } + +func (w writeCloser) Write(p []byte) (int, error) { + *w.b = append(*w.b, p...) + return len(p), nil +} + +func TestParseH2HeadersLoop_DecodesHPACKOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, sockPath) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 3*time.Second) }() + + // Build: preface + SETTINGS + HEADERS (stream 1, END_HEADERS). + // Headers in a specific order to verify HPACK decode order is preserved. + wantHeaders := [][2]string{ + {":method", "GET"}, + {":path", "/index.html"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {"accept", "text/html"}, + {"user-agent", "Go-http-client/2.0"}, + {"accept-language", "en-US"}, + } + + preface := buildH2Preface() + settings := buildH2Settings([][2]uint32{{0x3, 100}}) // MAX_CONCURRENT_STREAMS=100 + headers := buildH2HeadersFrame(1, wantHeaders, true) + + tap := make(chan []byte, 256) + + // Feed the preface through the tap. + chunks := [][]byte{preface, settings, headers} + for _, c := range chunks { + cp := make([]byte, len(c)) + copy(cp, c) + tap <- cp + } + close(tap) + + go parseH2HeadersLoop("5.6.7.8:443", tap) + + records := <-done + if len(records) == 0 { + t.Fatal("no records received") + } + rec := records[0] + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v", rec["kind"]) + } + if rec["proto_tag"] != "h2" { + t.Errorf("proto_tag: got %v", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v", rec["method"]) + } + if rec["path"] != "/index.html" { + t.Errorf("path: got %v", rec["path"]) + } + if rec["accept_language"] != "en-US" { + t.Errorf("accept_language: got %v", rec["accept_language"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != len(wantHeaders) { + t.Fatalf("want %d headers, got %d: %v", len(wantHeaders), len(ordered), ordered) + } + for i, pair := range ordered { + if pair[0] != wantHeaders[i][0] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], wantHeaders[i][0]) + } + } +} + +func TestParseH2Settings_FrameOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2s.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + // SETTINGS frame with 3 params in a specific order. + settings := [][2]uint32{ + {0x4, 65535}, // INITIAL_WINDOW_SIZE + {0x3, 1000}, // MAX_CONCURRENT_STREAMS + {0x1, 65536}, // HEADER_TABLE_SIZE + } + payload := make([]byte, len(settings)*6) + for i, s := range settings { + binary.BigEndian.PutUint16(payload[i*6:], uint16(s[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], s[1]) + } + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + parseAndSendH2Settings("1.2.3.4:1234", payload) + + rec := <-done + if rec["kind"] != "h2_settings" { + t.Errorf("kind: got %v", rec["kind"]) + } + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []float64 // JSON numbers decode as float64 + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 3 { + t.Fatalf("want 3 frame_order entries, got %d", len(order)) + } + if order[0] != 4 || order[1] != 3 || order[2] != 1 { + t.Errorf("frame_order: got %v, want [4 3 1]", order) + } +} diff --git a/decnet/templates/http/_caddy_modules/decnetfp/h3_tracer_test.go b/decnet/templates/http/_caddy_modules/decnetfp/h3_tracer_test.go new file mode 100644 index 00000000..a28d5984 --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/h3_tracer_test.go @@ -0,0 +1,170 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" +) + +// encodeVarint encodes a uint64 as an RFC 9000 variable-length integer. +func encodeVarint(v uint64) []byte { + switch { + case v <= 0x3f: + return []byte{byte(v)} + case v <= 0x3fff: + return []byte{0x40 | byte(v>>8), byte(v)} + case v <= 0x3fffffff: + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(v)|0x80000000) + return b + default: + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, v|0xc000000000000000) + return b + } +} + +// buildH3ControlStream builds the opening bytes of an h3 control stream +// with a SETTINGS frame containing the given id/val pairs. +func buildH3ControlStream(settings [][2]uint64) []byte { + // Stream type = 0x00 (control stream) + var body []byte + for _, s := range settings { + body = append(body, encodeVarint(s[0])...) + body = append(body, encodeVarint(s[1])...) + } + // h3 frame: type=0x04 (SETTINGS), length=len(body), body + frame := append(encodeVarint(0x04), encodeVarint(uint64(len(body)))...) + frame = append(frame, body...) + + return append(encodeVarint(0x00), frame...) +} + +func TestTryParseH3ControlStream_ParsesSettings(t *testing.T) { + sockPath := t.TempDir() + "/fp_h3.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + settings := [][2]uint64{ + {0x01, 0}, // QPACK_MAX_TABLE_CAPACITY + {0x06, 0}, // MAX_FIELD_SECTION_SIZE (0 = unlimited) + {0x07, 0}, // QPACK_BLOCKED_STREAMS + {0x4242, 1}, // GREASE-like unknown value + } + data := buildH3ControlStream(settings) + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + tryParseH3ControlStream("9.8.7.6:443", data) + + rec := <-done + if rec == nil { + t.Fatal("no record received") + } + if rec["kind"] != "h3_settings" { + t.Errorf("kind: got %v, want h3_settings", rec["kind"]) + } + if rec["remote_addr"] != "9.8.7.6:443" { + t.Errorf("remote_addr: got %v", rec["remote_addr"]) + } + + rawSettings, _ := json.Marshal(rec["settings"]) + var gotSettings map[string]interface{} + json.Unmarshal(rawSettings, &gotSettings) //nolint:errcheck + if _, ok := gotSettings["QPACK_MAX_TABLE_CAPACITY"]; !ok { + t.Errorf("missing QPACK_MAX_TABLE_CAPACITY in settings: %v", gotSettings) + } + if _, ok := gotSettings["MAX_FIELD_SECTION_SIZE"]; !ok { + t.Errorf("missing MAX_FIELD_SECTION_SIZE in settings: %v", gotSettings) + } + + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []interface{} + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 4 { + t.Errorf("want 4 frame_order entries, got %d: %v", len(order), order) + } +} + +func TestTryParseH3ControlStream_WrongStreamType(t *testing.T) { + // Stream type 0x02 = QPACK encoder stream — should be ignored. + data := append(encodeVarint(0x02), []byte{0x00}...) + // Should not panic or emit any record. + tryParseH3ControlStream("1.2.3.4:443", data) // no socket — will silently drop +} + +func TestTryParseH3ControlStream_TruncatedData(t *testing.T) { + // Only stream-type prefix, no SETTINGS frame yet. + data := encodeVarint(0x00) + tryParseH3ControlStream("1.2.3.4:443", data) // must not panic +} + +func TestQuicVarint(t *testing.T) { + cases := []struct { + input []byte + want uint64 + wantN int + }{ + {[]byte{0x00}, 0, 1}, + {[]byte{0x3f}, 63, 1}, + {[]byte{0x40, 0x00}, 0, 2}, + {[]byte{0x7f, 0xff}, 16383, 2}, + {[]byte{0x80, 0x00, 0x00, 0x00}, 0, 4}, + {[]byte{0xbf, 0xff, 0xff, 0xff}, 1073741823, 4}, + // Empty input + {[]byte{}, 0, 0}, + // Truncated 2-byte + {[]byte{0x40}, 0, 0}, + } + for _, c := range cases { + v, n := quicVarint(c.input) + if v != c.want || n != c.wantN { + t.Errorf("quicVarint(%x) = (%d, %d), want (%d, %d)", c.input, v, n, c.want, c.wantN) + } + } +} + +func TestH3SettingName(t *testing.T) { + cases := []struct { + id uint64 + want string + }{ + {0x01, "QPACK_MAX_TABLE_CAPACITY"}, + {0x06, "MAX_FIELD_SECTION_SIZE"}, + {0x07, "QPACK_BLOCKED_STREAMS"}, + {0x08, "ENABLE_CONNECT_PROTOCOL"}, + {0x33, "H3_DATAGRAM"}, + {0x41, "UNKNOWN"}, // not a GREASE pattern (GREASE = 0x1f*N+0x21; 0x41-0x21=0x20, not div by 0x1f) + } + for _, c := range cases { + if got := h3SettingName(c.id); got != c.want { + t.Errorf("h3SettingName(0x%x) = %q, want %q", c.id, got, c.want) + } + } +} diff --git a/decnet/templates/http/_caddy_modules/decnetfp/h3app.go b/decnet/templates/http/_caddy_modules/decnetfp/h3app.go new file mode 100644 index 00000000..3e09b642 --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/h3app.go @@ -0,0 +1,178 @@ +package decnetfp + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "strings" + "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "go.uber.org/zap" +) + +func init() { + caddy.RegisterModule(H3App{}) + httpcaddyfile.RegisterGlobalOption("decnet_h3", parseH3AppOption) +} + +// parseH3AppOption maps the `decnet_h3` global Caddyfile block to the +// decnet_h3 app config (empty JSON — all config comes from env). +func parseH3AppOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { + for d.Next() { + for d.NextBlock(0) { + } + } + return json.RawMessage(`{}`), nil +} + +// H3App is a Caddy app that owns the QUIC/UDP listener on port 443, forwarding +// accepted h3 connections to the Caddy HTTP app's handler chain. This is the +// only way to inject a per-connection quic-go Tracer for h3 SETTINGS capture, +// since Caddy does not expose its QUIC config. +// +// Activate with a `decnet_h3` global Caddyfile block AND omit `h3` from the +// `:443` server's `protocols` list, otherwise both this app and Caddy's HTTP +// server will fight over UDP/443. +// +// The app is a no-op when `HTTP_VERSIONS` env does not contain `"http/3"`. +type H3App struct { + caddyCtx caddy.Context + logger *zap.Logger + listener *quic.Listener + transport *quic.Transport + httpSrv *http3.Server + cancelLoop context.CancelFunc + wg *sync.WaitGroup +} + +func (H3App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "decnet_h3", + New: func() caddy.Module { return new(H3App) }, + } +} + +func (a *H3App) Provision(ctx caddy.Context) error { + a.caddyCtx = ctx + a.logger = ctx.Logger() + return nil +} + +func (a *H3App) Start() error { + a.wg = &sync.WaitGroup{} + if !strings.Contains(os.Getenv("HTTP_VERSIONS"), "http/3") { + return nil + } + + cert, err := tls.LoadX509KeyPair("/opt/tls/cert.pem", "/opt/tls/key.pem") + if err != nil { + return fmt.Errorf("decnet_h3: load TLS cert: %w", err) + } + tlsCfg := &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"h3"}, + } + + udpConn, err := net.ListenPacket("udp", ":443") + if err != nil { + return fmt.Errorf("decnet_h3: bind UDP/443: %w", err) + } + tr := &quic.Transport{Conn: udpConn.(*net.UDPConn)} + a.transport = tr + + ln, err := tr.Listen(tlsCfg, &quic.Config{Tracer: newH3SettingsTracer}) + if err != nil { + tr.Close() + return fmt.Errorf("decnet_h3: quic listen: %w", err) + } + a.listener = ln + + handler, err := a.findHTTPHandler() + if err != nil { + ln.Close() + tr.Close() + return fmt.Errorf("decnet_h3: find HTTP handler: %w", err) + } + + a.httpSrv = &http3.Server{Handler: handler} + + loopCtx, cancel := context.WithCancel(context.Background()) + a.cancelLoop = cancel + a.wg.Add(1) + go func() { + defer a.wg.Done() + a.acceptLoop(loopCtx) + }() + + a.logger.Info("decnet_h3 listening on UDP/443") + return nil +} + +func (a *H3App) acceptLoop(ctx context.Context) { + for { + conn, err := a.listener.Accept(ctx) + if err != nil { + return + } + wrapped := &h3SettingsTappingConn{ + Connection: conn, + remoteAddr: conn.RemoteAddr().String(), + } + go func() { + a.httpSrv.ServeQUICConn(wrapped) //nolint:errcheck + }() + } +} + +func (a *H3App) Stop() error { + if a.cancelLoop != nil { + a.cancelLoop() + } + if a.listener != nil { + a.listener.Close() + } + a.wg.Wait() + if a.transport != nil { + a.transport.Close() + } + return nil +} + +// findHTTPHandler returns the http.Handler for Caddy's :443 server. +func (a *H3App) findHTTPHandler() (http.Handler, error) { + appIface, err := a.caddyCtx.App("http") + if err != nil { + return nil, fmt.Errorf("get http app: %w", err) + } + httpApp, ok := appIface.(*caddyhttp.App) + if !ok { + return nil, fmt.Errorf("unexpected http app type %T", appIface) + } + for _, srv := range httpApp.Servers { + for _, addr := range srv.Listen { + if strings.Contains(addr, ":443") { + return srv, nil + } + } + } + // Fall back to any available server. + for _, srv := range httpApp.Servers { + return srv, nil + } + return nil, fmt.Errorf("no HTTP servers found in caddy http app") +} + +var ( + _ caddy.App = (*H3App)(nil) + _ caddy.Provisioner = (*H3App)(nil) +) diff --git a/decnet/templates/http/_caddy_modules/decnetfp/h3conn.go b/decnet/templates/http/_caddy_modules/decnetfp/h3conn.go new file mode 100644 index 00000000..ccf3f82b --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/h3conn.go @@ -0,0 +1,184 @@ +package decnetfp + +import ( + "bytes" + "context" + "io" + "sync" + "time" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" +) + +// newH3SettingsTracer is the quic.Config.Tracer factory. We don't use +// quic-go's logging.ConnectionTracer for SETTINGS (its ReceivedStreamFrame +// hook gives only metadata, not payload bytes). The actual h3 SETTINGS +// capture happens in h3TappingUniStream by wrapping AcceptUniStream. +// This function returns nil (no-op tracer) so quic-go uses its default path. +func newH3SettingsTracer(_ context.Context, _ logging.Perspective, _ quic.ConnectionID) *logging.ConnectionTracer { + return nil +} + +// ── QUIC connection wrapper ─────────────────────────────────────────────────── + +// h3SettingsTappingConn wraps quic.Connection and intercepts AcceptUniStream +// so the first bytes of each client-initiated unidirectional stream can be +// inspected for h3 control stream SETTINGS before being replayed to the +// http3.Server. +type h3SettingsTappingConn struct { + quic.Connection + remoteAddr string +} + +func (c *h3SettingsTappingConn) AcceptUniStream(ctx context.Context) (quic.ReceiveStream, error) { + stream, err := c.Connection.AcceptUniStream(ctx) + if err != nil { + return stream, err + } + return &h3TappingUniStream{ReceiveStream: stream, remoteAddr: c.remoteAddr}, nil +} + +// ── QUIC receive-stream wrapper ─────────────────────────────────────────────── + +// h3TappingUniStream peeks at the first bytes of a unidirectional stream to +// identify the h3 control stream (stream type 0x00, RFC 9114 §6.2.1) and +// extract its first SETTINGS frame, then replays all bytes to the caller. +type h3TappingUniStream struct { + quic.ReceiveStream + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// maxH3ControlPeek is enough to cover the stream-type varint + SETTINGS +// frame type varint + frame-length varint + a typical SETTINGS frame body +// (6 settings × 8 bytes each = 48 bytes, plus 3 varint headers ≈ 64 bytes). +const maxH3ControlPeek = 256 + +func (s *h3TappingUniStream) Read(p []byte) (int, error) { + s.once.Do(func() { + scratch := make([]byte, maxH3ControlPeek) + n, _ := s.ReceiveStream.Read(scratch) + s.buf.Write(scratch[:n]) + go tryParseH3ControlStream(s.remoteAddr, s.buf.Bytes()) + s.reader = io.MultiReader(&s.buf, s.ReceiveStream) + }) + if s.reader != nil { + return s.reader.Read(p) + } + return s.ReceiveStream.Read(p) +} + +// tryParseH3ControlStream examines the peeked bytes. If the stream opens +// with stream-type 0x00 (h3 control stream) and the first frame is SETTINGS +// (type 0x04), it emits an h3_settings fp record. All errors are silent — +// this is a best-effort tap. +func tryParseH3ControlStream(remoteAddr string, data []byte) { + streamType, c0 := quicVarint(data) + if c0 == 0 || streamType != 0x00 { + return // not the h3 control stream + } + data = data[c0:] + + frameType, c1 := quicVarint(data) + if c1 == 0 { + return + } + data = data[c1:] + + frameLen, c2 := quicVarint(data) + if c2 == 0 { + return + } + data = data[c2:] + + // Per RFC 9114 §7.2.4: the first frame on the control stream MUST be SETTINGS. + if frameType != 0x04 { + return + } + if uint64(len(data)) < frameLen { + return // need more bytes — we only peeked 256 + } + body := data[:frameLen] + + settings := make(map[string]uint64) + frameOrder := make([]uint64, 0, 8) + for len(body) > 0 { + id, ci := quicVarint(body) + if ci == 0 { + break + } + body = body[ci:] + val, cv := quicVarint(body) + if cv == 0 { + break + } + body = body[cv:] + settings[h3SettingName(id)] = val + frameOrder = append(frameOrder, id) + } + + sendFP(map[string]interface{}{ + "kind": "h3_settings", + "remote_addr": remoteAddr, + "settings": settings, + "frame_order": frameOrder, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// quicVarint decodes an RFC 9000 §16 variable-length integer. +// Returns (value, bytes_consumed); bytes_consumed == 0 on failure. +func quicVarint(b []byte) (uint64, int) { + if len(b) == 0 { + return 0, 0 + } + prefix := b[0] >> 6 + switch prefix { + case 0: + return uint64(b[0] & 0x3f), 1 + case 1: + if len(b) < 2 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<8 | uint64(b[1]), 2 + case 2: + if len(b) < 4 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 + case 3: + if len(b) < 8 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | + uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 + } + return 0, 0 +} + +// h3SettingName maps RFC 9114 and extension SETTINGS IDs to human-readable names. +func h3SettingName(id uint64) string { + switch id { + case 0x01: + return "QPACK_MAX_TABLE_CAPACITY" + case 0x06: + return "MAX_FIELD_SECTION_SIZE" + case 0x07: + return "QPACK_BLOCKED_STREAMS" + case 0x08: + return "ENABLE_CONNECT_PROTOCOL" + case 0x33: + return "H3_DATAGRAM" + case 0xc671706a: + return "ENABLE_WEBTRANSPORT" + default: + // GREASE values per RFC 9114 §7.2.8 pattern (0x1f * N + 0x21) + if id > 0x20 && (id-0x21)%0x1f == 0 { + return "GREASE" + } + return "UNKNOWN" + } +} diff --git a/decnet/templates/http/_caddy_modules/decnetfp/module.go b/decnet/templates/http/_caddy_modules/decnetfp/module.go new file mode 100644 index 00000000..4ebdce99 --- /dev/null +++ b/decnet/templates/http/_caddy_modules/decnetfp/module.go @@ -0,0 +1,645 @@ +// Package decnetfp provides Caddy modules for HTTP fingerprint capture. +// +// Registered modules: +// - caddy.listeners.decnet_fp — post-TLS listener wrapper that taps +// the h2 client preface (SETTINGS + HEADERS frames via persistent HPACK +// decoder) and h1 request-line / header bytes, emitting ordered header +// name lists to /run/decnet/fp.sock (unix datagram). +// - http.handlers.decnet_fp — HTTP middleware that emits an +// access_log record (status code, bytes, protocol) after each response. +// - caddy.logging.encoders.decnet_jsonl — log encoder stub (registered +// but not wired into Caddyfile; access_log comes from the handler above). +// +// All modules write JSON lines to a unix datagram socket whose path is +// controlled by DECNET_FP_SOCK (default: /run/decnet/fp.sock). +package decnetfp + +import ( + "bytes" + "crypto/tls" + "encoding/binary" + "encoding/json" + "io" + "net" + "net/http" + "os" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "go.uber.org/zap" + "golang.org/x/net/http2/hpack" +) + +func init() { + caddy.RegisterModule(FPListenerWrapper{}) + caddy.RegisterModule(FPHandler{}) + caddy.RegisterModule(DecnetJSONLEncoder{}) + httpcaddyfile.RegisterHandlerDirective("decnet_fp", parseFPHandler) +} + +func parseFPHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + var fp FPHandler + return &fp, fp.UnmarshalCaddyfile(h.Dispenser) +} + +func sockPath() string { + if p := os.Getenv("DECNET_FP_SOCK"); p != "" { + return p + } + return "/run/decnet/fp.sock" +} + +// ── unix datagram sender ────────────────────────────────────────────────────── + +var ( + sockMu sync.Mutex + sockConn *net.UnixConn +) + +func sendFP(record map[string]interface{}) { + b, err := json.Marshal(record) + if err != nil { + return + } + sockMu.Lock() + defer sockMu.Unlock() + if sockConn == nil { + conn, err := net.DialUnix("unixgram", nil, &net.UnixAddr{Name: sockPath(), Net: "unixgram"}) + if err != nil { + return + } + sockConn = conn + } + sockConn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)) //nolint:errcheck + sockConn.Write(b) //nolint:errcheck +} + +// ── caddy.listeners.decnet_fp ───────────────────────────────────────────────── + +// FPListenerWrapper is a post-TLS Caddy listener wrapper that: +// - For h2 ALPN connections: taps the h2 client preface (SETTINGS frame) +// and then continuously parses HEADERS/CONTINUATION frames via a +// persistent HPACK decoder, emitting ordered header name lists. +// - For h1 ALPN connections (or plain connections): taps the first request's +// header bytes, emitting ordered header names from the wire. +// +// Place it AFTER the TLS listener wrapper so it sees post-TLS data: +// +// listener_wrappers { +// tls +// decnet_fp +// } +type FPListenerWrapper struct { + logger *zap.Logger +} + +func (FPListenerWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.decnet_fp", + New: func() caddy.Module { return new(FPListenerWrapper) }, + } +} + +func (w *FPListenerWrapper) Provision(ctx caddy.Context) error { + w.logger = ctx.Logger() + return nil +} + +func (w *FPListenerWrapper) WrapListener(ln net.Listener) net.Listener { + return &fpListener{Listener: ln, logger: w.logger} +} + +func (w *FPListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +type fpListener struct { + net.Listener + logger *zap.Logger +} + +func (l *fpListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return conn, err + } + remote := conn.RemoteAddr().String() + + tlsConn, ok := conn.(*tls.Conn) + if !ok { + // Plain (cleartext) connection — peek to distinguish h2c from h1. + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil + } + state := tlsConn.ConnectionState() + switch state.NegotiatedProtocol { + case "h2": + return &h2TappingConn{Conn: conn, remoteAddr: remote}, nil + default: + // http/1.1 ALPN or no ALPN — post-TLS plaintext is h1; safe to + // use plainTappingConn (h2c preface won't appear after TLS). + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil + } +} + +// ── Plain connection tap (h1 and h2c) ──────────────────────────────────────── + +const h1MaxHeaderBuf = 8192 + +// plainTappingConn handles post-accept connections that are not TLS h2. +// It peeks the first bytes to distinguish h2c prior-knowledge from h1, +// then routes to the appropriate parser. Used for both cleartext (port 80) +// and TLS-h1 (ALPN "" or "http/1.1") connections. +type plainTappingConn struct { + net.Conn + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// tapWriter is a non-blocking io.Writer that drops if the channel is full. +type tapWriter struct { + ch chan<- []byte +} + +func (t tapWriter) Write(p []byte) (int, error) { + cp := make([]byte, len(p)) + copy(cp, p) + select { + case t.ch <- cp: + default: + } + return len(p), nil +} + +func (c *plainTappingConn) Read(b []byte) (int, error) { + c.once.Do(func() { + // Peek exactly len(h2ClientPreface) bytes to detect h2c prior knowledge. + preface := make([]byte, len(h2ClientPreface)) + n, _ := io.ReadFull(c.Conn, preface) + c.buf.Write(preface[:n]) + + if n == len(h2ClientPreface) && string(preface) == h2ClientPreface { + // h2c prior-knowledge connection — run the same SETTINGS+HPACK tap. + hdr9 := make([]byte, 9) + if m, err := io.ReadFull(c.Conn, hdr9); m == 9 && err == nil { + c.buf.Write(hdr9) + frameLen := int(hdr9[0])<<16 | int(hdr9[1])<<8 | int(hdr9[2]) + frameType := hdr9[3] + if frameType == 0x4 && frameLen > 0 && frameLen <= 16384 { + payload := make([]byte, frameLen) + if _, err := io.ReadFull(c.Conn, payload); err == nil { + c.buf.Write(payload) + go parseAndSendH2Settings(c.remoteAddr, payload) + } + } + } + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) + return + } + + // h1 — buffer up to h1MaxHeaderBuf or until \r\n\r\n. + scratch := make([]byte, h1MaxHeaderBuf) + for c.buf.Len() < h1MaxHeaderBuf { + nn, err := c.Conn.Read(scratch[:h1MaxHeaderBuf-c.buf.Len()]) + c.buf.Write(scratch[:nn]) + if bytes.Contains(c.buf.Bytes(), []byte("\r\n\r\n")) { + break + } + if err != nil { + break + } + } + go parseAndSendH1Headers(c.remoteAddr, c.buf.Bytes()) + c.reader = io.MultiReader(&c.buf, c.Conn) + }) + if c.reader == nil { + return c.Conn.Read(b) + } + return c.reader.Read(b) +} + +func parseAndSendH1Headers(remoteAddr string, raw []byte) { + idx := bytes.Index(raw, []byte("\r\n\r\n")) + if idx < 0 { + idx = len(raw) + } + lines := bytes.Split(raw[:idx], []byte("\r\n")) + if len(lines) == 0 { + return + } + // First line: "GET /path HTTP/1.1" + requestLine := string(lines[0]) + var method, path, proto string + parts := bytes.Fields(lines[0]) + if len(parts) >= 3 { + method = string(parts[0]) + path = string(parts[1]) + proto = string(parts[2]) + } + + var ordered [][]string // [[name, value], ...] + var cookie, acceptLang string + for _, line := range lines[1:] { + sep := bytes.IndexByte(line, ':') + if sep < 0 { + continue + } + name := string(bytes.ToLower(bytes.TrimSpace(line[:sep]))) + value := string(bytes.TrimSpace(line[sep+1:])) + ordered = append(ordered, []string{name, value}) + switch name { + case "cookie": + cookie = value + case "accept-language": + acceptLang = value + } + } + + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "proto_tag": "h1", + "request_line": requestLine, + "method": method, + "path": path, + "proto": proto, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// ── H2 client preface + HPACK continuous tap ────────────────────────────────── + +const h2ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + +type h2TappingConn struct { + net.Conn + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +func (c *h2TappingConn) Read(b []byte) (int, error) { + c.once.Do(func() { + // Buffer client preface (24 B) + first frame header (9 B). + hdr := make([]byte, len(h2ClientPreface)+9) + if _, err := io.ReadFull(c.Conn, hdr); err != nil { + c.buf.Write(hdr) + c.reader = io.MultiReader(&c.buf, c.Conn) + return + } + c.buf.Write(hdr) + + frameLen := int(hdr[len(h2ClientPreface)])<<16 | + int(hdr[len(h2ClientPreface)+1])<<8 | + int(hdr[len(h2ClientPreface)+2]) + frameType := hdr[len(h2ClientPreface)+3] + + if frameType == 0x4 && frameLen > 0 && frameLen <= 16384 { + payload := make([]byte, frameLen) + if _, err := io.ReadFull(c.Conn, payload); err == nil { + c.buf.Write(payload) + go parseAndSendH2Settings(c.remoteAddr, payload) + } + } + + // Start HPACK continuous parse on a non-blocking channel tap. + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) + }) + if c.reader == nil { + return c.Conn.Read(b) + } + return c.reader.Read(b) +} + +func parseAndSendH2Settings(remoteAddr string, payload []byte) { + settings := make(map[string]uint32) + frameOrder := make([]uint16, 0, len(payload)/6) + for i := 0; i+6 <= len(payload); i += 6 { + id := binary.BigEndian.Uint16(payload[i : i+2]) + val := binary.BigEndian.Uint32(payload[i+2 : i+6]) + settings[settingName(id)] = val + frameOrder = append(frameOrder, id) + } + sendFP(map[string]interface{}{ + "kind": "h2_settings", + "remote_addr": remoteAddr, + "settings": settings, + "frame_order": frameOrder, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +func settingName(id uint16) string { + switch id { + case 0x1: + return "HEADER_TABLE_SIZE" + case 0x2: + return "ENABLE_PUSH" + case 0x3: + return "MAX_CONCURRENT_STREAMS" + case 0x4: + return "INITIAL_WINDOW_SIZE" + case 0x5: + return "MAX_FRAME_SIZE" + case 0x6: + return "MAX_HEADER_LIST_SIZE" + case 0x8: + return "ENABLE_CONNECT_PROTOCOL" + default: + if id >= 0xf000 { + return "GREASE" + } + return "UNKNOWN" + } +} + +// hpackConn holds per-connection HPACK decoder state. +type hpackConn struct { + decoder *hpack.Decoder + currentFields []hpack.HeaderField +} + +func newHpackConn(maxTableSize uint32) *hpackConn { + hc := &hpackConn{} + hc.decoder = hpack.NewDecoder(maxTableSize, func(f hpack.HeaderField) { + hc.currentFields = append(hc.currentFields, f) + }) + return hc +} + +// decode decodes a header block fragment, appending to existing and returning combined. +func (hc *hpackConn) decode(fragment []byte, existing []hpack.HeaderField) []hpack.HeaderField { + hc.currentFields = existing + hc.decoder.Write(fragment) //nolint:errcheck + return hc.currentFields +} + +func parseH2HeadersLoop(remoteAddr string, tap <-chan []byte) { + var buf []byte + prefaceLen := len(h2ClientPreface) + prefaceSkipped := false + hc := newHpackConn(4096) + + type streamState struct { + fields []hpack.HeaderField + } + streams := make(map[uint32]*streamState) + + for chunk := range tap { + buf = append(buf, chunk...) + + if !prefaceSkipped { + if len(buf) < prefaceLen { + continue + } + buf = buf[prefaceLen:] + prefaceSkipped = true + } + + for len(buf) >= 9 { + frameLen := int(buf[0])<<16 | int(buf[1])<<8 | int(buf[2]) + frameType := buf[3] + flags := buf[4] + streamID := binary.BigEndian.Uint32(buf[5:9]) & 0x7fffffff + + total := 9 + frameLen + if len(buf) < total { + break + } + payload := buf[9:total] + buf = buf[total:] + + switch frameType { + case 0x4: // SETTINGS from client + if flags&0x1 != 0 { // ACK + break + } + for i := 0; i+6 <= len(payload); i += 6 { + id := binary.BigEndian.Uint16(payload[i : i+2]) + val := binary.BigEndian.Uint32(payload[i+2 : i+6]) + if id == 0x1 { // SETTINGS_HEADER_TABLE_SIZE + hc.decoder.SetMaxDynamicTableSize(val) + } + } + + case 0x1, 0x9: // HEADERS, CONTINUATION + fragment := payload + if frameType == 0x1 { + off := 0 + var padLen byte + if flags&0x08 != 0 { // PADDED + if len(fragment) < 1 { + continue + } + padLen = fragment[0] + off = 1 + } + if flags&0x20 != 0 { // PRIORITY + off += 5 + } + end := len(fragment) - int(padLen) + if off > end || end < 0 { + continue + } + fragment = fragment[off:end] + } + + ss, ok := streams[streamID] + if !ok { + ss = &streamState{} + streams[streamID] = ss + } + ss.fields = hc.decode(fragment, ss.fields) + + if flags&0x04 != 0 { // END_HEADERS + emitH2RequestHeaders(remoteAddr, streamID, ss.fields) + delete(streams, streamID) + } + } + } + } +} + +func emitH2RequestHeaders(remoteAddr string, streamID uint32, fields []hpack.HeaderField) { + ordered := make([][]string, 0, len(fields)) + var cookie, acceptLang, method, path string + for _, f := range fields { + ordered = append(ordered, []string{f.Name, f.Value}) + switch f.Name { + case "cookie": + cookie = f.Value + case "accept-language": + acceptLang = f.Value + case ":method": + method = f.Value + case ":path": + path = f.Value + } + } + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "stream_id": streamID, + "proto_tag": "h2", + "method": method, + "path": path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// ── http.handlers.decnet_fp ─────────────────────────────────────────────────── + +// FPHandler is HTTP middleware that emits an access_log record (status code, +// bytes, proto) after each response via the fp socket. For h3 requests it +// also emits a best-effort http_request_headers record (header order degraded +// — QPACK decode order is preserved by quic-go but the Go map randomises it +// further; canonical h3 header order requires request-stream QPACK tapping +// which is a follow-up task). +type FPHandler struct { + logger *zap.Logger +} + +func (FPHandler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.decnet_fp", + New: func() caddy.Module { return new(FPHandler) }, + } +} + +func (h *FPHandler) Provision(ctx caddy.Context) error { + h.logger = ctx.Logger() + return nil +} + +func (h *FPHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// responseCapture captures the status code and bytes written. +type responseCapture struct { + http.ResponseWriter + status int + bytes int +} + +func (rc *responseCapture) WriteHeader(status int) { + rc.status = status + rc.ResponseWriter.WriteHeader(status) +} + +func (rc *responseCapture) Write(b []byte) (int, error) { + n, err := rc.ResponseWriter.Write(b) + rc.bytes += n + if rc.status == 0 { + rc.status = 200 + } + return n, err +} + +func (h *FPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + rc := &responseCapture{ResponseWriter: w} + err := next.ServeHTTP(rc, r) + + protoTag := "h1" + if r.ProtoMajor == 2 { + protoTag = "h2" + } else if r.ProtoMajor == 3 { + protoTag = "h3" + } + + status := rc.status + if status == 0 { + status = 200 + } + + go sendFP(map[string]interface{}{ + "kind": "access_log", + "remote_addr": r.RemoteAddr, + "method": r.Method, + "path": r.URL.Path, + "proto": r.Proto, + "proto_tag": protoTag, + "status": status, + "bytes": rc.bytes, + "ts": time.Now().UTC().Format(time.RFC3339), + }) + + // For h3, emit best-effort http_request_headers (map order, degraded). + if r.ProtoMajor == 3 { + ordered := make([][]string, 0, len(r.Header)) + var cookie, acceptLang string + for name, vals := range r.Header { + v := "" + if len(vals) > 0 { + v = vals[0] + } + ordered = append(ordered, []string{name, v}) + switch http.CanonicalHeaderKey(name) { + case "Cookie": + cookie = v + case "Accept-Language": + acceptLang = v + } + } + go sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": r.RemoteAddr, + "proto_tag": "h3", + "method": r.Method, + "path": r.URL.Path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "h3_order_note": "degraded_map_iteration", + "ts": time.Now().UTC().Format(time.RFC3339), + }) + } + + return err +} + +var ( + _ caddy.Provisioner = (*FPListenerWrapper)(nil) + _ caddy.ListenerWrapper = (*FPListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*FPListenerWrapper)(nil) + _ caddy.Provisioner = (*FPHandler)(nil) + _ caddyhttp.MiddlewareHandler = (*FPHandler)(nil) + _ caddyfile.Unmarshaler = (*FPHandler)(nil) +) + +// ── caddy.logging.encoders.decnet_jsonl ────────────────────────────────────── + +// DecnetJSONLEncoder is a registered Caddy module stub. A full zapcore.Encoder +// implementation (required for `log { format decnet_jsonl }`) is deferred; +// access_log records are emitted by FPHandler.ServeHTTP instead. +type DecnetJSONLEncoder struct { + logger *zap.Logger +} + +func (DecnetJSONLEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.decnet_jsonl", + New: func() caddy.Module { return new(DecnetJSONLEncoder) }, + } +} + +func (e *DecnetJSONLEncoder) Provision(ctx caddy.Context) error { + e.logger = ctx.Logger() + return nil +} diff --git a/decnet/templates/http/entrypoint.sh b/decnet/templates/http/entrypoint.sh index aba79672..bce5a804 100644 --- a/decnet/templates/http/entrypoint.sh +++ b/decnet/templates/http/entrypoint.sh @@ -21,6 +21,9 @@ cat > /etc/caddy/Caddyfile < None: def forward_syslog(line: str, log_target: str) -> None: """No-op stub. TCP forwarding is handled by rsyslog, not by service containers.""" pass + + +# ─── JA4H (local copy — containers can't import from decnet.sniffer) ───────── + + +def _sha256_12(s: str) -> str: + return _hashlib.sha256(s.encode()).hexdigest()[:12] + + +def _compute_ja4h( + method: str, + proto: str, + headers_ordered: list, + cookie: str = "", + accept_lang: str = "", +) -> str: + """Compute JA4H per the FoxIO public spec. + + headers_ordered is a list of [name, value] pairs (or bare name strings). + """ + method_tag = (method[:2].upper() if method else "UN") + ver_map = { + "HTTP/1.0": "10", "HTTP/1.1": "11", "HTTP/2.0": "20", "HTTP/3.0": "30", + "H1": "11", "H2": "20", "H3": "30", + "h1": "11", "h2": "20", "h3": "30", + } + ver_tag = ver_map.get(proto.upper(), "00") + names = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers_ordered + ] + has_cookie = "c" if any(n == "cookie" for n in names) else "n" + has_referer = "r" if any(n == "referer" for n in names) else "n" + lang_tag = (accept_lang[:4].ljust(4, "0") if accept_lang else "0000") + filtered = [n for n in names if n not in ("cookie", "referer")] + count_tag = f"{min(len(filtered), 99):02d}" + header_hash = _sha256_12(",".join(filtered)) + if cookie: + pairs = sorted(p.strip() for p in cookie.split(";") if "=" in p.strip()) + cookie_hash = _sha256_12(";".join(pairs)) + else: + cookie_hash = "000000000000" + return f"{method_tag}{ver_tag}{has_cookie}{has_referer}{lang_tag}_{count_tag}_{header_hash}_{cookie_hash}" + + +# ─── Caddy fingerprint socket reader ───────────────────────────────────────── + +_FP_BUF = 65536 + + +def _fp_socket_reader(node_name: str, service_name: str, log_target: str) -> None: + sock_path = _os.environ.get("DECNET_FP_SOCK", "/run/decnet/fp.sock") + try: + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM) + sock.bind(sock_path) + except OSError: + return + while True: + try: + data = sock.recv(_FP_BUF) + record = _json.loads(data) + except (OSError, ValueError): + continue + kind = record.get("kind", "") + remote = record.get("remote_addr", "-") + + if kind == "h2_settings": + ln = syslog_line( + service_name, node_name, "http2_settings", SEVERITY_INFO, + remote_addr=remote, + settings=_json.dumps(record.get("settings", {})), + frame_order=_json.dumps(record.get("frame_order", [])), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "h3_settings": + ln = syslog_line( + service_name, node_name, "http3_settings", SEVERITY_INFO, + remote_addr=remote, + settings=_json.dumps(record.get("settings", {})), + frame_order=_json.dumps(record.get("frame_order", [])), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "http_request_headers": + # Canonical header order from the listener wrapper. + headers = record.get("headers_ordered", []) + method = record.get("method", "") + proto = record.get("proto_tag", "h1") + cookie = record.get("cookie", "") + accept_lang = record.get("accept_language", "") + ja4h = _compute_ja4h(method, proto, headers, cookie, accept_lang) + names_only = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers + ] + ln = syslog_line( + service_name, node_name, "http_request_fingerprint", SEVERITY_INFO, + remote_addr=remote, + proto=proto, + method=method, + path=record.get("path", ""), + ja4h=ja4h, + headers_ordered=_json.dumps(names_only), + cookie=cookie, + accept_language=accept_lang, + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "access_log": + ln = syslog_line( + service_name, node_name, "http_access", SEVERITY_INFO, + remote_addr=remote, + method=record.get("method", ""), + path=record.get("path", ""), + proto=record.get("proto_tag", "-"), + status=str(record.get("status", 0)), + bytes=str(record.get("bytes", 0)), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + +def start_fp_socket_reader(node_name: str, service_name: str, log_target: str) -> None: + t = _threading.Thread( + target=_fp_socket_reader, + args=(node_name, service_name, log_target), + daemon=True, + ) + t.start() diff --git a/decnet/templates/https/_caddy_modules/decnetfp/go.mod b/decnet/templates/https/_caddy_modules/decnetfp/go.mod new file mode 100644 index 00000000..1759f457 --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/go.mod @@ -0,0 +1,116 @@ +module github.com/decnet/caddy-fp + +go 1.22 + +require ( + github.com/caddyserver/caddy/v2 v2.8.4 + github.com/quic-go/quic-go v0.44.0 + go.uber.org/zap v1.27.0 + golang.org/x/net v0.27.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/caddyserver/certmagic v0.21.3 // indirect + github.com/caddyserver/zerossl v0.1.3 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/dgraph-io/badger v1.6.2 // indirect + github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/cel-go v0.20.1 // indirect + github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/libdns/libdns v0.2.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mholt/acmez/v2 v2.0.1 // indirect + github.com/miekg/dns v1.1.59 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.13.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/slackhq/nebula v1.6.1 // indirect + github.com/smallstep/certificates v0.26.1 // indirect + github.com/smallstep/nosql v0.6.1 // indirect + github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect + github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect + github.com/smallstep/truststore v0.13.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect + github.com/urfave/cli v1.22.14 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.step.sm/cli-utils v0.9.0 // indirect + go.step.sm/crypto v0.45.0 // indirect + go.step.sm/linkedca v0.20.1 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/mock v0.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.2.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.0 // indirect +) diff --git a/decnet/templates/https/_caddy_modules/decnetfp/go.sum b/decnet/templates/https/_caddy_modules/decnetfp/go.sum new file mode 100644 index 00000000..c902cb6b --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/go.sum @@ -0,0 +1,592 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= +github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= +github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= +github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk= +github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw= +github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= +github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= +github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= +github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= +github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= +github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= +github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= +github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= +github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= +github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= +github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= +github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= +github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= +github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= +github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= +github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU= +github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= +github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= +go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= +go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= +go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= +go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= +go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= +golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= +google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/decnet/templates/https/_caddy_modules/decnetfp/h1_test.go b/decnet/templates/https/_caddy_modules/decnetfp/h1_test.go new file mode 100644 index 00000000..7d8e5ecf --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/h1_test.go @@ -0,0 +1,150 @@ +package decnetfp + +import ( + "encoding/json" + "net" + "testing" + "time" +) + +// bindSock creates a unix datagram socket at path and returns it. +// The caller must close it (or register a t.Cleanup). +func bindSock(t *testing.T, path string) *net.UnixConn { + t.Helper() + sock, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: path, Net: "unixgram"}) + if err != nil { + t.Fatalf("listen %s: %v", path, err) + } + return sock +} + +// drainSock reads up to n records from an already-bound unix datagram socket. +func drainSock(t *testing.T, sock *net.UnixConn, count int, timeout time.Duration) []map[string]interface{} { + t.Helper() + sock.SetDeadline(time.Now().Add(timeout)) //nolint:errcheck + var records []map[string]interface{} + buf := make([]byte, 65536) + for len(records) < count { + n, err := sock.Read(buf) + if err != nil { + break + } + var m map[string]interface{} + if err := json.Unmarshal(buf[:n], &m); err != nil { + t.Logf("unmarshal: %v", err) + continue + } + records = append(records, m) + } + return records +} + +func TestParseAndSendH1Headers(t *testing.T) { + path := t.TempDir() + "/fp_h1.sock" + t.Setenv("DECNET_FP_SOCK", path) + // Reset the global socket so it reconnects to the test socket. + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { + done <- drainSock(t, srv, 1, 2*time.Second) + }() + + // Typical curl HTTP/1.1 request bytes. + raw := "GET /robots.txt HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: curl/8.0.1\r\n" + + "Accept: */*\r\n" + + "X-Custom-A: alpha\r\n" + + "X-Custom-B: beta\r\n" + + "\r\n" + + parseAndSendH1Headers("1.2.3.4:9999", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rec := records[0] + + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v, want http_request_headers", rec["kind"]) + } + if rec["proto_tag"] != "h1" { + t.Errorf("proto_tag: got %v, want h1", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v, want GET", rec["method"]) + } + if rec["path"] != "/robots.txt" { + t.Errorf("path: got %v, want /robots.txt", rec["path"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != 5 { + t.Fatalf("want 5 headers, got %d: %v", len(ordered), ordered) + } + // Wire order must be preserved exactly. + want := []string{"host", "user-agent", "accept", "x-custom-a", "x-custom-b"} + for i, pair := range ordered { + if pair[0] != want[i] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], want[i]) + } + } +} + +func TestParseAndSendH1Headers_StopsAtEmptyLine(t *testing.T) { + // Headers should not include body bytes after \r\n\r\n. + raw := "POST /login HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nuser=bob&pass=secret" + path := t.TempDir() + "/fp_h1b.sock" + t.Setenv("DECNET_FP_SOCK", path) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, path) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 2*time.Second) }() + parseAndSendH1Headers("10.0.0.1:1234", []byte(raw)) + + records := <-done + if len(records) != 1 { + t.Fatalf("want 1 record, got %d", len(records)) + } + rawOrdered, _ := json.Marshal(records[0]["headers_ordered"]) + var ordered [][]string + json.Unmarshal(rawOrdered, &ordered) //nolint:errcheck + if len(ordered) != 1 { + t.Fatalf("want 1 header (Content-Type), got %d: %v", len(ordered), ordered) + } + if ordered[0][0] != "content-type" { + t.Errorf("header[0]: got %q, want content-type", ordered[0][0]) + } +} diff --git a/decnet/templates/https/_caddy_modules/decnetfp/h2_test.go b/decnet/templates/https/_caddy_modules/decnetfp/h2_test.go new file mode 100644 index 00000000..f5c11551 --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/h2_test.go @@ -0,0 +1,206 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" + + "golang.org/x/net/http2/hpack" +) + +// buildH2Preface returns the 24-byte h2 client preface. +func buildH2Preface() []byte { + return []byte(h2ClientPreface) +} + +// buildH2Settings builds a SETTINGS frame payload from id/val pairs. +func buildH2Settings(pairs [][2]uint32) []byte { + payload := make([]byte, len(pairs)*6) + for i, p := range pairs { + binary.BigEndian.PutUint16(payload[i*6:], uint16(p[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], p[1]) + } + return buildH2Frame(0x4, 0, 0, payload) +} + +// buildH2Frame builds a raw h2 frame (9-byte header + payload). +func buildH2Frame(typ, flags byte, streamID uint32, payload []byte) []byte { + frame := make([]byte, 9+len(payload)) + frame[0] = byte(len(payload) >> 16) + frame[1] = byte(len(payload) >> 8) + frame[2] = byte(len(payload)) + frame[3] = typ + frame[4] = flags + binary.BigEndian.PutUint32(frame[5:], streamID) + copy(frame[9:], payload) + return frame +} + +// buildH2HeadersFrame encodes headers with HPACK and wraps in a HEADERS frame. +func buildH2HeadersFrame(streamID uint32, headers [][2]string, endHeaders bool) []byte { + var hbuf []byte + enc := hpack.NewEncoder(writeCloser{&hbuf}) + for _, h := range headers { + enc.WriteField(hpack.HeaderField{Name: h[0], Value: h[1]}) //nolint:errcheck + } + flags := byte(0) + if endHeaders { + flags |= 0x04 + } + return buildH2Frame(0x1, flags, streamID, hbuf) +} + +type writeCloser struct{ b *[]byte } + +func (w writeCloser) Write(p []byte) (int, error) { + *w.b = append(*w.b, p...) + return len(p), nil +} + +func TestParseH2HeadersLoop_DecodesHPACKOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv := bindSock(t, sockPath) + t.Cleanup(func() { srv.Close() }) + + done := make(chan []map[string]interface{}, 1) + go func() { done <- drainSock(t, srv, 1, 3*time.Second) }() + + // Build: preface + SETTINGS + HEADERS (stream 1, END_HEADERS). + // Headers in a specific order to verify HPACK decode order is preserved. + wantHeaders := [][2]string{ + {":method", "GET"}, + {":path", "/index.html"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {"accept", "text/html"}, + {"user-agent", "Go-http-client/2.0"}, + {"accept-language", "en-US"}, + } + + preface := buildH2Preface() + settings := buildH2Settings([][2]uint32{{0x3, 100}}) // MAX_CONCURRENT_STREAMS=100 + headers := buildH2HeadersFrame(1, wantHeaders, true) + + tap := make(chan []byte, 256) + + // Feed the preface through the tap. + chunks := [][]byte{preface, settings, headers} + for _, c := range chunks { + cp := make([]byte, len(c)) + copy(cp, c) + tap <- cp + } + close(tap) + + go parseH2HeadersLoop("5.6.7.8:443", tap) + + records := <-done + if len(records) == 0 { + t.Fatal("no records received") + } + rec := records[0] + if rec["kind"] != "http_request_headers" { + t.Errorf("kind: got %v", rec["kind"]) + } + if rec["proto_tag"] != "h2" { + t.Errorf("proto_tag: got %v", rec["proto_tag"]) + } + if rec["method"] != "GET" { + t.Errorf("method: got %v", rec["method"]) + } + if rec["path"] != "/index.html" { + t.Errorf("path: got %v", rec["path"]) + } + if rec["accept_language"] != "en-US" { + t.Errorf("accept_language: got %v", rec["accept_language"]) + } + + rawOrdered, _ := json.Marshal(rec["headers_ordered"]) + var ordered [][]string + if err := json.Unmarshal(rawOrdered, &ordered); err != nil { + t.Fatalf("unmarshal headers_ordered: %v", err) + } + if len(ordered) != len(wantHeaders) { + t.Fatalf("want %d headers, got %d: %v", len(wantHeaders), len(ordered), ordered) + } + for i, pair := range ordered { + if pair[0] != wantHeaders[i][0] { + t.Errorf("header[%d]: got %q, want %q", i, pair[0], wantHeaders[i][0]) + } + } +} + +func TestParseH2Settings_FrameOrder(t *testing.T) { + sockPath := t.TempDir() + "/fp_h2s.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + // SETTINGS frame with 3 params in a specific order. + settings := [][2]uint32{ + {0x4, 65535}, // INITIAL_WINDOW_SIZE + {0x3, 1000}, // MAX_CONCURRENT_STREAMS + {0x1, 65536}, // HEADER_TABLE_SIZE + } + payload := make([]byte, len(settings)*6) + for i, s := range settings { + binary.BigEndian.PutUint16(payload[i*6:], uint16(s[0])) + binary.BigEndian.PutUint32(payload[i*6+2:], s[1]) + } + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + parseAndSendH2Settings("1.2.3.4:1234", payload) + + rec := <-done + if rec["kind"] != "h2_settings" { + t.Errorf("kind: got %v", rec["kind"]) + } + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []float64 // JSON numbers decode as float64 + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 3 { + t.Fatalf("want 3 frame_order entries, got %d", len(order)) + } + if order[0] != 4 || order[1] != 3 || order[2] != 1 { + t.Errorf("frame_order: got %v, want [4 3 1]", order) + } +} diff --git a/decnet/templates/https/_caddy_modules/decnetfp/h3_tracer_test.go b/decnet/templates/https/_caddy_modules/decnetfp/h3_tracer_test.go new file mode 100644 index 00000000..a28d5984 --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/h3_tracer_test.go @@ -0,0 +1,170 @@ +package decnetfp + +import ( + "encoding/binary" + "encoding/json" + "net" + "testing" + "time" +) + +// encodeVarint encodes a uint64 as an RFC 9000 variable-length integer. +func encodeVarint(v uint64) []byte { + switch { + case v <= 0x3f: + return []byte{byte(v)} + case v <= 0x3fff: + return []byte{0x40 | byte(v>>8), byte(v)} + case v <= 0x3fffffff: + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(v)|0x80000000) + return b + default: + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, v|0xc000000000000000) + return b + } +} + +// buildH3ControlStream builds the opening bytes of an h3 control stream +// with a SETTINGS frame containing the given id/val pairs. +func buildH3ControlStream(settings [][2]uint64) []byte { + // Stream type = 0x00 (control stream) + var body []byte + for _, s := range settings { + body = append(body, encodeVarint(s[0])...) + body = append(body, encodeVarint(s[1])...) + } + // h3 frame: type=0x04 (SETTINGS), length=len(body), body + frame := append(encodeVarint(0x04), encodeVarint(uint64(len(body)))...) + frame = append(frame, body...) + + return append(encodeVarint(0x00), frame...) +} + +func TestTryParseH3ControlStream_ParsesSettings(t *testing.T) { + sockPath := t.TempDir() + "/fp_h3.sock" + t.Setenv("DECNET_FP_SOCK", sockPath) + sockMu.Lock() + sockConn = nil + sockMu.Unlock() + t.Cleanup(func() { + sockMu.Lock() + if sockConn != nil { + sockConn.Close() + sockConn = nil + } + sockMu.Unlock() + }) + + srv, err := net.ListenUnixgram("unixgram", &net.UnixAddr{Name: sockPath, Net: "unixgram"}) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + settings := [][2]uint64{ + {0x01, 0}, // QPACK_MAX_TABLE_CAPACITY + {0x06, 0}, // MAX_FIELD_SECTION_SIZE (0 = unlimited) + {0x07, 0}, // QPACK_BLOCKED_STREAMS + {0x4242, 1}, // GREASE-like unknown value + } + data := buildH3ControlStream(settings) + + done := make(chan map[string]interface{}, 1) + go func() { + buf := make([]byte, 65536) + srv.SetDeadline(time.Now().Add(2 * time.Second)) + n, _ := srv.Read(buf) + var m map[string]interface{} + json.Unmarshal(buf[:n], &m) //nolint:errcheck + done <- m + }() + + tryParseH3ControlStream("9.8.7.6:443", data) + + rec := <-done + if rec == nil { + t.Fatal("no record received") + } + if rec["kind"] != "h3_settings" { + t.Errorf("kind: got %v, want h3_settings", rec["kind"]) + } + if rec["remote_addr"] != "9.8.7.6:443" { + t.Errorf("remote_addr: got %v", rec["remote_addr"]) + } + + rawSettings, _ := json.Marshal(rec["settings"]) + var gotSettings map[string]interface{} + json.Unmarshal(rawSettings, &gotSettings) //nolint:errcheck + if _, ok := gotSettings["QPACK_MAX_TABLE_CAPACITY"]; !ok { + t.Errorf("missing QPACK_MAX_TABLE_CAPACITY in settings: %v", gotSettings) + } + if _, ok := gotSettings["MAX_FIELD_SECTION_SIZE"]; !ok { + t.Errorf("missing MAX_FIELD_SECTION_SIZE in settings: %v", gotSettings) + } + + rawOrder, _ := json.Marshal(rec["frame_order"]) + var order []interface{} + json.Unmarshal(rawOrder, &order) //nolint:errcheck + if len(order) != 4 { + t.Errorf("want 4 frame_order entries, got %d: %v", len(order), order) + } +} + +func TestTryParseH3ControlStream_WrongStreamType(t *testing.T) { + // Stream type 0x02 = QPACK encoder stream — should be ignored. + data := append(encodeVarint(0x02), []byte{0x00}...) + // Should not panic or emit any record. + tryParseH3ControlStream("1.2.3.4:443", data) // no socket — will silently drop +} + +func TestTryParseH3ControlStream_TruncatedData(t *testing.T) { + // Only stream-type prefix, no SETTINGS frame yet. + data := encodeVarint(0x00) + tryParseH3ControlStream("1.2.3.4:443", data) // must not panic +} + +func TestQuicVarint(t *testing.T) { + cases := []struct { + input []byte + want uint64 + wantN int + }{ + {[]byte{0x00}, 0, 1}, + {[]byte{0x3f}, 63, 1}, + {[]byte{0x40, 0x00}, 0, 2}, + {[]byte{0x7f, 0xff}, 16383, 2}, + {[]byte{0x80, 0x00, 0x00, 0x00}, 0, 4}, + {[]byte{0xbf, 0xff, 0xff, 0xff}, 1073741823, 4}, + // Empty input + {[]byte{}, 0, 0}, + // Truncated 2-byte + {[]byte{0x40}, 0, 0}, + } + for _, c := range cases { + v, n := quicVarint(c.input) + if v != c.want || n != c.wantN { + t.Errorf("quicVarint(%x) = (%d, %d), want (%d, %d)", c.input, v, n, c.want, c.wantN) + } + } +} + +func TestH3SettingName(t *testing.T) { + cases := []struct { + id uint64 + want string + }{ + {0x01, "QPACK_MAX_TABLE_CAPACITY"}, + {0x06, "MAX_FIELD_SECTION_SIZE"}, + {0x07, "QPACK_BLOCKED_STREAMS"}, + {0x08, "ENABLE_CONNECT_PROTOCOL"}, + {0x33, "H3_DATAGRAM"}, + {0x41, "UNKNOWN"}, // not a GREASE pattern (GREASE = 0x1f*N+0x21; 0x41-0x21=0x20, not div by 0x1f) + } + for _, c := range cases { + if got := h3SettingName(c.id); got != c.want { + t.Errorf("h3SettingName(0x%x) = %q, want %q", c.id, got, c.want) + } + } +} diff --git a/decnet/templates/https/_caddy_modules/decnetfp/h3app.go b/decnet/templates/https/_caddy_modules/decnetfp/h3app.go new file mode 100644 index 00000000..3e09b642 --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/h3app.go @@ -0,0 +1,178 @@ +package decnetfp + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "strings" + "sync" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" + "go.uber.org/zap" +) + +func init() { + caddy.RegisterModule(H3App{}) + httpcaddyfile.RegisterGlobalOption("decnet_h3", parseH3AppOption) +} + +// parseH3AppOption maps the `decnet_h3` global Caddyfile block to the +// decnet_h3 app config (empty JSON — all config comes from env). +func parseH3AppOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) { + for d.Next() { + for d.NextBlock(0) { + } + } + return json.RawMessage(`{}`), nil +} + +// H3App is a Caddy app that owns the QUIC/UDP listener on port 443, forwarding +// accepted h3 connections to the Caddy HTTP app's handler chain. This is the +// only way to inject a per-connection quic-go Tracer for h3 SETTINGS capture, +// since Caddy does not expose its QUIC config. +// +// Activate with a `decnet_h3` global Caddyfile block AND omit `h3` from the +// `:443` server's `protocols` list, otherwise both this app and Caddy's HTTP +// server will fight over UDP/443. +// +// The app is a no-op when `HTTP_VERSIONS` env does not contain `"http/3"`. +type H3App struct { + caddyCtx caddy.Context + logger *zap.Logger + listener *quic.Listener + transport *quic.Transport + httpSrv *http3.Server + cancelLoop context.CancelFunc + wg *sync.WaitGroup +} + +func (H3App) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "decnet_h3", + New: func() caddy.Module { return new(H3App) }, + } +} + +func (a *H3App) Provision(ctx caddy.Context) error { + a.caddyCtx = ctx + a.logger = ctx.Logger() + return nil +} + +func (a *H3App) Start() error { + a.wg = &sync.WaitGroup{} + if !strings.Contains(os.Getenv("HTTP_VERSIONS"), "http/3") { + return nil + } + + cert, err := tls.LoadX509KeyPair("/opt/tls/cert.pem", "/opt/tls/key.pem") + if err != nil { + return fmt.Errorf("decnet_h3: load TLS cert: %w", err) + } + tlsCfg := &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"h3"}, + } + + udpConn, err := net.ListenPacket("udp", ":443") + if err != nil { + return fmt.Errorf("decnet_h3: bind UDP/443: %w", err) + } + tr := &quic.Transport{Conn: udpConn.(*net.UDPConn)} + a.transport = tr + + ln, err := tr.Listen(tlsCfg, &quic.Config{Tracer: newH3SettingsTracer}) + if err != nil { + tr.Close() + return fmt.Errorf("decnet_h3: quic listen: %w", err) + } + a.listener = ln + + handler, err := a.findHTTPHandler() + if err != nil { + ln.Close() + tr.Close() + return fmt.Errorf("decnet_h3: find HTTP handler: %w", err) + } + + a.httpSrv = &http3.Server{Handler: handler} + + loopCtx, cancel := context.WithCancel(context.Background()) + a.cancelLoop = cancel + a.wg.Add(1) + go func() { + defer a.wg.Done() + a.acceptLoop(loopCtx) + }() + + a.logger.Info("decnet_h3 listening on UDP/443") + return nil +} + +func (a *H3App) acceptLoop(ctx context.Context) { + for { + conn, err := a.listener.Accept(ctx) + if err != nil { + return + } + wrapped := &h3SettingsTappingConn{ + Connection: conn, + remoteAddr: conn.RemoteAddr().String(), + } + go func() { + a.httpSrv.ServeQUICConn(wrapped) //nolint:errcheck + }() + } +} + +func (a *H3App) Stop() error { + if a.cancelLoop != nil { + a.cancelLoop() + } + if a.listener != nil { + a.listener.Close() + } + a.wg.Wait() + if a.transport != nil { + a.transport.Close() + } + return nil +} + +// findHTTPHandler returns the http.Handler for Caddy's :443 server. +func (a *H3App) findHTTPHandler() (http.Handler, error) { + appIface, err := a.caddyCtx.App("http") + if err != nil { + return nil, fmt.Errorf("get http app: %w", err) + } + httpApp, ok := appIface.(*caddyhttp.App) + if !ok { + return nil, fmt.Errorf("unexpected http app type %T", appIface) + } + for _, srv := range httpApp.Servers { + for _, addr := range srv.Listen { + if strings.Contains(addr, ":443") { + return srv, nil + } + } + } + // Fall back to any available server. + for _, srv := range httpApp.Servers { + return srv, nil + } + return nil, fmt.Errorf("no HTTP servers found in caddy http app") +} + +var ( + _ caddy.App = (*H3App)(nil) + _ caddy.Provisioner = (*H3App)(nil) +) diff --git a/decnet/templates/https/_caddy_modules/decnetfp/h3conn.go b/decnet/templates/https/_caddy_modules/decnetfp/h3conn.go new file mode 100644 index 00000000..ccf3f82b --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/h3conn.go @@ -0,0 +1,184 @@ +package decnetfp + +import ( + "bytes" + "context" + "io" + "sync" + "time" + + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/logging" +) + +// newH3SettingsTracer is the quic.Config.Tracer factory. We don't use +// quic-go's logging.ConnectionTracer for SETTINGS (its ReceivedStreamFrame +// hook gives only metadata, not payload bytes). The actual h3 SETTINGS +// capture happens in h3TappingUniStream by wrapping AcceptUniStream. +// This function returns nil (no-op tracer) so quic-go uses its default path. +func newH3SettingsTracer(_ context.Context, _ logging.Perspective, _ quic.ConnectionID) *logging.ConnectionTracer { + return nil +} + +// ── QUIC connection wrapper ─────────────────────────────────────────────────── + +// h3SettingsTappingConn wraps quic.Connection and intercepts AcceptUniStream +// so the first bytes of each client-initiated unidirectional stream can be +// inspected for h3 control stream SETTINGS before being replayed to the +// http3.Server. +type h3SettingsTappingConn struct { + quic.Connection + remoteAddr string +} + +func (c *h3SettingsTappingConn) AcceptUniStream(ctx context.Context) (quic.ReceiveStream, error) { + stream, err := c.Connection.AcceptUniStream(ctx) + if err != nil { + return stream, err + } + return &h3TappingUniStream{ReceiveStream: stream, remoteAddr: c.remoteAddr}, nil +} + +// ── QUIC receive-stream wrapper ─────────────────────────────────────────────── + +// h3TappingUniStream peeks at the first bytes of a unidirectional stream to +// identify the h3 control stream (stream type 0x00, RFC 9114 §6.2.1) and +// extract its first SETTINGS frame, then replays all bytes to the caller. +type h3TappingUniStream struct { + quic.ReceiveStream + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// maxH3ControlPeek is enough to cover the stream-type varint + SETTINGS +// frame type varint + frame-length varint + a typical SETTINGS frame body +// (6 settings × 8 bytes each = 48 bytes, plus 3 varint headers ≈ 64 bytes). +const maxH3ControlPeek = 256 + +func (s *h3TappingUniStream) Read(p []byte) (int, error) { + s.once.Do(func() { + scratch := make([]byte, maxH3ControlPeek) + n, _ := s.ReceiveStream.Read(scratch) + s.buf.Write(scratch[:n]) + go tryParseH3ControlStream(s.remoteAddr, s.buf.Bytes()) + s.reader = io.MultiReader(&s.buf, s.ReceiveStream) + }) + if s.reader != nil { + return s.reader.Read(p) + } + return s.ReceiveStream.Read(p) +} + +// tryParseH3ControlStream examines the peeked bytes. If the stream opens +// with stream-type 0x00 (h3 control stream) and the first frame is SETTINGS +// (type 0x04), it emits an h3_settings fp record. All errors are silent — +// this is a best-effort tap. +func tryParseH3ControlStream(remoteAddr string, data []byte) { + streamType, c0 := quicVarint(data) + if c0 == 0 || streamType != 0x00 { + return // not the h3 control stream + } + data = data[c0:] + + frameType, c1 := quicVarint(data) + if c1 == 0 { + return + } + data = data[c1:] + + frameLen, c2 := quicVarint(data) + if c2 == 0 { + return + } + data = data[c2:] + + // Per RFC 9114 §7.2.4: the first frame on the control stream MUST be SETTINGS. + if frameType != 0x04 { + return + } + if uint64(len(data)) < frameLen { + return // need more bytes — we only peeked 256 + } + body := data[:frameLen] + + settings := make(map[string]uint64) + frameOrder := make([]uint64, 0, 8) + for len(body) > 0 { + id, ci := quicVarint(body) + if ci == 0 { + break + } + body = body[ci:] + val, cv := quicVarint(body) + if cv == 0 { + break + } + body = body[cv:] + settings[h3SettingName(id)] = val + frameOrder = append(frameOrder, id) + } + + sendFP(map[string]interface{}{ + "kind": "h3_settings", + "remote_addr": remoteAddr, + "settings": settings, + "frame_order": frameOrder, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// quicVarint decodes an RFC 9000 §16 variable-length integer. +// Returns (value, bytes_consumed); bytes_consumed == 0 on failure. +func quicVarint(b []byte) (uint64, int) { + if len(b) == 0 { + return 0, 0 + } + prefix := b[0] >> 6 + switch prefix { + case 0: + return uint64(b[0] & 0x3f), 1 + case 1: + if len(b) < 2 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<8 | uint64(b[1]), 2 + case 2: + if len(b) < 4 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 + case 3: + if len(b) < 8 { + return 0, 0 + } + return uint64(b[0]&0x3f)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | + uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 + } + return 0, 0 +} + +// h3SettingName maps RFC 9114 and extension SETTINGS IDs to human-readable names. +func h3SettingName(id uint64) string { + switch id { + case 0x01: + return "QPACK_MAX_TABLE_CAPACITY" + case 0x06: + return "MAX_FIELD_SECTION_SIZE" + case 0x07: + return "QPACK_BLOCKED_STREAMS" + case 0x08: + return "ENABLE_CONNECT_PROTOCOL" + case 0x33: + return "H3_DATAGRAM" + case 0xc671706a: + return "ENABLE_WEBTRANSPORT" + default: + // GREASE values per RFC 9114 §7.2.8 pattern (0x1f * N + 0x21) + if id > 0x20 && (id-0x21)%0x1f == 0 { + return "GREASE" + } + return "UNKNOWN" + } +} diff --git a/decnet/templates/https/_caddy_modules/decnetfp/module.go b/decnet/templates/https/_caddy_modules/decnetfp/module.go new file mode 100644 index 00000000..4ebdce99 --- /dev/null +++ b/decnet/templates/https/_caddy_modules/decnetfp/module.go @@ -0,0 +1,645 @@ +// Package decnetfp provides Caddy modules for HTTP fingerprint capture. +// +// Registered modules: +// - caddy.listeners.decnet_fp — post-TLS listener wrapper that taps +// the h2 client preface (SETTINGS + HEADERS frames via persistent HPACK +// decoder) and h1 request-line / header bytes, emitting ordered header +// name lists to /run/decnet/fp.sock (unix datagram). +// - http.handlers.decnet_fp — HTTP middleware that emits an +// access_log record (status code, bytes, protocol) after each response. +// - caddy.logging.encoders.decnet_jsonl — log encoder stub (registered +// but not wired into Caddyfile; access_log comes from the handler above). +// +// All modules write JSON lines to a unix datagram socket whose path is +// controlled by DECNET_FP_SOCK (default: /run/decnet/fp.sock). +package decnetfp + +import ( + "bytes" + "crypto/tls" + "encoding/binary" + "encoding/json" + "io" + "net" + "net/http" + "os" + "sync" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "go.uber.org/zap" + "golang.org/x/net/http2/hpack" +) + +func init() { + caddy.RegisterModule(FPListenerWrapper{}) + caddy.RegisterModule(FPHandler{}) + caddy.RegisterModule(DecnetJSONLEncoder{}) + httpcaddyfile.RegisterHandlerDirective("decnet_fp", parseFPHandler) +} + +func parseFPHandler(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + var fp FPHandler + return &fp, fp.UnmarshalCaddyfile(h.Dispenser) +} + +func sockPath() string { + if p := os.Getenv("DECNET_FP_SOCK"); p != "" { + return p + } + return "/run/decnet/fp.sock" +} + +// ── unix datagram sender ────────────────────────────────────────────────────── + +var ( + sockMu sync.Mutex + sockConn *net.UnixConn +) + +func sendFP(record map[string]interface{}) { + b, err := json.Marshal(record) + if err != nil { + return + } + sockMu.Lock() + defer sockMu.Unlock() + if sockConn == nil { + conn, err := net.DialUnix("unixgram", nil, &net.UnixAddr{Name: sockPath(), Net: "unixgram"}) + if err != nil { + return + } + sockConn = conn + } + sockConn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)) //nolint:errcheck + sockConn.Write(b) //nolint:errcheck +} + +// ── caddy.listeners.decnet_fp ───────────────────────────────────────────────── + +// FPListenerWrapper is a post-TLS Caddy listener wrapper that: +// - For h2 ALPN connections: taps the h2 client preface (SETTINGS frame) +// and then continuously parses HEADERS/CONTINUATION frames via a +// persistent HPACK decoder, emitting ordered header name lists. +// - For h1 ALPN connections (or plain connections): taps the first request's +// header bytes, emitting ordered header names from the wire. +// +// Place it AFTER the TLS listener wrapper so it sees post-TLS data: +// +// listener_wrappers { +// tls +// decnet_fp +// } +type FPListenerWrapper struct { + logger *zap.Logger +} + +func (FPListenerWrapper) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.listeners.decnet_fp", + New: func() caddy.Module { return new(FPListenerWrapper) }, + } +} + +func (w *FPListenerWrapper) Provision(ctx caddy.Context) error { + w.logger = ctx.Logger() + return nil +} + +func (w *FPListenerWrapper) WrapListener(ln net.Listener) net.Listener { + return &fpListener{Listener: ln, logger: w.logger} +} + +func (w *FPListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +type fpListener struct { + net.Listener + logger *zap.Logger +} + +func (l *fpListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return conn, err + } + remote := conn.RemoteAddr().String() + + tlsConn, ok := conn.(*tls.Conn) + if !ok { + // Plain (cleartext) connection — peek to distinguish h2c from h1. + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil + } + state := tlsConn.ConnectionState() + switch state.NegotiatedProtocol { + case "h2": + return &h2TappingConn{Conn: conn, remoteAddr: remote}, nil + default: + // http/1.1 ALPN or no ALPN — post-TLS plaintext is h1; safe to + // use plainTappingConn (h2c preface won't appear after TLS). + return &plainTappingConn{Conn: conn, remoteAddr: remote}, nil + } +} + +// ── Plain connection tap (h1 and h2c) ──────────────────────────────────────── + +const h1MaxHeaderBuf = 8192 + +// plainTappingConn handles post-accept connections that are not TLS h2. +// It peeks the first bytes to distinguish h2c prior-knowledge from h1, +// then routes to the appropriate parser. Used for both cleartext (port 80) +// and TLS-h1 (ALPN "" or "http/1.1") connections. +type plainTappingConn struct { + net.Conn + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +// tapWriter is a non-blocking io.Writer that drops if the channel is full. +type tapWriter struct { + ch chan<- []byte +} + +func (t tapWriter) Write(p []byte) (int, error) { + cp := make([]byte, len(p)) + copy(cp, p) + select { + case t.ch <- cp: + default: + } + return len(p), nil +} + +func (c *plainTappingConn) Read(b []byte) (int, error) { + c.once.Do(func() { + // Peek exactly len(h2ClientPreface) bytes to detect h2c prior knowledge. + preface := make([]byte, len(h2ClientPreface)) + n, _ := io.ReadFull(c.Conn, preface) + c.buf.Write(preface[:n]) + + if n == len(h2ClientPreface) && string(preface) == h2ClientPreface { + // h2c prior-knowledge connection — run the same SETTINGS+HPACK tap. + hdr9 := make([]byte, 9) + if m, err := io.ReadFull(c.Conn, hdr9); m == 9 && err == nil { + c.buf.Write(hdr9) + frameLen := int(hdr9[0])<<16 | int(hdr9[1])<<8 | int(hdr9[2]) + frameType := hdr9[3] + if frameType == 0x4 && frameLen > 0 && frameLen <= 16384 { + payload := make([]byte, frameLen) + if _, err := io.ReadFull(c.Conn, payload); err == nil { + c.buf.Write(payload) + go parseAndSendH2Settings(c.remoteAddr, payload) + } + } + } + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) + return + } + + // h1 — buffer up to h1MaxHeaderBuf or until \r\n\r\n. + scratch := make([]byte, h1MaxHeaderBuf) + for c.buf.Len() < h1MaxHeaderBuf { + nn, err := c.Conn.Read(scratch[:h1MaxHeaderBuf-c.buf.Len()]) + c.buf.Write(scratch[:nn]) + if bytes.Contains(c.buf.Bytes(), []byte("\r\n\r\n")) { + break + } + if err != nil { + break + } + } + go parseAndSendH1Headers(c.remoteAddr, c.buf.Bytes()) + c.reader = io.MultiReader(&c.buf, c.Conn) + }) + if c.reader == nil { + return c.Conn.Read(b) + } + return c.reader.Read(b) +} + +func parseAndSendH1Headers(remoteAddr string, raw []byte) { + idx := bytes.Index(raw, []byte("\r\n\r\n")) + if idx < 0 { + idx = len(raw) + } + lines := bytes.Split(raw[:idx], []byte("\r\n")) + if len(lines) == 0 { + return + } + // First line: "GET /path HTTP/1.1" + requestLine := string(lines[0]) + var method, path, proto string + parts := bytes.Fields(lines[0]) + if len(parts) >= 3 { + method = string(parts[0]) + path = string(parts[1]) + proto = string(parts[2]) + } + + var ordered [][]string // [[name, value], ...] + var cookie, acceptLang string + for _, line := range lines[1:] { + sep := bytes.IndexByte(line, ':') + if sep < 0 { + continue + } + name := string(bytes.ToLower(bytes.TrimSpace(line[:sep]))) + value := string(bytes.TrimSpace(line[sep+1:])) + ordered = append(ordered, []string{name, value}) + switch name { + case "cookie": + cookie = value + case "accept-language": + acceptLang = value + } + } + + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "proto_tag": "h1", + "request_line": requestLine, + "method": method, + "path": path, + "proto": proto, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// ── H2 client preface + HPACK continuous tap ────────────────────────────────── + +const h2ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + +type h2TappingConn struct { + net.Conn + once sync.Once + buf bytes.Buffer + reader io.Reader + remoteAddr string +} + +func (c *h2TappingConn) Read(b []byte) (int, error) { + c.once.Do(func() { + // Buffer client preface (24 B) + first frame header (9 B). + hdr := make([]byte, len(h2ClientPreface)+9) + if _, err := io.ReadFull(c.Conn, hdr); err != nil { + c.buf.Write(hdr) + c.reader = io.MultiReader(&c.buf, c.Conn) + return + } + c.buf.Write(hdr) + + frameLen := int(hdr[len(h2ClientPreface)])<<16 | + int(hdr[len(h2ClientPreface)+1])<<8 | + int(hdr[len(h2ClientPreface)+2]) + frameType := hdr[len(h2ClientPreface)+3] + + if frameType == 0x4 && frameLen > 0 && frameLen <= 16384 { + payload := make([]byte, frameLen) + if _, err := io.ReadFull(c.Conn, payload); err == nil { + c.buf.Write(payload) + go parseAndSendH2Settings(c.remoteAddr, payload) + } + } + + // Start HPACK continuous parse on a non-blocking channel tap. + tap := make(chan []byte, 256) + c.reader = io.TeeReader(io.MultiReader(&c.buf, c.Conn), tapWriter{ch: tap}) + go parseH2HeadersLoop(c.remoteAddr, tap) + }) + if c.reader == nil { + return c.Conn.Read(b) + } + return c.reader.Read(b) +} + +func parseAndSendH2Settings(remoteAddr string, payload []byte) { + settings := make(map[string]uint32) + frameOrder := make([]uint16, 0, len(payload)/6) + for i := 0; i+6 <= len(payload); i += 6 { + id := binary.BigEndian.Uint16(payload[i : i+2]) + val := binary.BigEndian.Uint32(payload[i+2 : i+6]) + settings[settingName(id)] = val + frameOrder = append(frameOrder, id) + } + sendFP(map[string]interface{}{ + "kind": "h2_settings", + "remote_addr": remoteAddr, + "settings": settings, + "frame_order": frameOrder, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +func settingName(id uint16) string { + switch id { + case 0x1: + return "HEADER_TABLE_SIZE" + case 0x2: + return "ENABLE_PUSH" + case 0x3: + return "MAX_CONCURRENT_STREAMS" + case 0x4: + return "INITIAL_WINDOW_SIZE" + case 0x5: + return "MAX_FRAME_SIZE" + case 0x6: + return "MAX_HEADER_LIST_SIZE" + case 0x8: + return "ENABLE_CONNECT_PROTOCOL" + default: + if id >= 0xf000 { + return "GREASE" + } + return "UNKNOWN" + } +} + +// hpackConn holds per-connection HPACK decoder state. +type hpackConn struct { + decoder *hpack.Decoder + currentFields []hpack.HeaderField +} + +func newHpackConn(maxTableSize uint32) *hpackConn { + hc := &hpackConn{} + hc.decoder = hpack.NewDecoder(maxTableSize, func(f hpack.HeaderField) { + hc.currentFields = append(hc.currentFields, f) + }) + return hc +} + +// decode decodes a header block fragment, appending to existing and returning combined. +func (hc *hpackConn) decode(fragment []byte, existing []hpack.HeaderField) []hpack.HeaderField { + hc.currentFields = existing + hc.decoder.Write(fragment) //nolint:errcheck + return hc.currentFields +} + +func parseH2HeadersLoop(remoteAddr string, tap <-chan []byte) { + var buf []byte + prefaceLen := len(h2ClientPreface) + prefaceSkipped := false + hc := newHpackConn(4096) + + type streamState struct { + fields []hpack.HeaderField + } + streams := make(map[uint32]*streamState) + + for chunk := range tap { + buf = append(buf, chunk...) + + if !prefaceSkipped { + if len(buf) < prefaceLen { + continue + } + buf = buf[prefaceLen:] + prefaceSkipped = true + } + + for len(buf) >= 9 { + frameLen := int(buf[0])<<16 | int(buf[1])<<8 | int(buf[2]) + frameType := buf[3] + flags := buf[4] + streamID := binary.BigEndian.Uint32(buf[5:9]) & 0x7fffffff + + total := 9 + frameLen + if len(buf) < total { + break + } + payload := buf[9:total] + buf = buf[total:] + + switch frameType { + case 0x4: // SETTINGS from client + if flags&0x1 != 0 { // ACK + break + } + for i := 0; i+6 <= len(payload); i += 6 { + id := binary.BigEndian.Uint16(payload[i : i+2]) + val := binary.BigEndian.Uint32(payload[i+2 : i+6]) + if id == 0x1 { // SETTINGS_HEADER_TABLE_SIZE + hc.decoder.SetMaxDynamicTableSize(val) + } + } + + case 0x1, 0x9: // HEADERS, CONTINUATION + fragment := payload + if frameType == 0x1 { + off := 0 + var padLen byte + if flags&0x08 != 0 { // PADDED + if len(fragment) < 1 { + continue + } + padLen = fragment[0] + off = 1 + } + if flags&0x20 != 0 { // PRIORITY + off += 5 + } + end := len(fragment) - int(padLen) + if off > end || end < 0 { + continue + } + fragment = fragment[off:end] + } + + ss, ok := streams[streamID] + if !ok { + ss = &streamState{} + streams[streamID] = ss + } + ss.fields = hc.decode(fragment, ss.fields) + + if flags&0x04 != 0 { // END_HEADERS + emitH2RequestHeaders(remoteAddr, streamID, ss.fields) + delete(streams, streamID) + } + } + } + } +} + +func emitH2RequestHeaders(remoteAddr string, streamID uint32, fields []hpack.HeaderField) { + ordered := make([][]string, 0, len(fields)) + var cookie, acceptLang, method, path string + for _, f := range fields { + ordered = append(ordered, []string{f.Name, f.Value}) + switch f.Name { + case "cookie": + cookie = f.Value + case "accept-language": + acceptLang = f.Value + case ":method": + method = f.Value + case ":path": + path = f.Value + } + } + sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": remoteAddr, + "stream_id": streamID, + "proto_tag": "h2", + "method": method, + "path": path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "ts": time.Now().UTC().Format(time.RFC3339), + }) +} + +// ── http.handlers.decnet_fp ─────────────────────────────────────────────────── + +// FPHandler is HTTP middleware that emits an access_log record (status code, +// bytes, proto) after each response via the fp socket. For h3 requests it +// also emits a best-effort http_request_headers record (header order degraded +// — QPACK decode order is preserved by quic-go but the Go map randomises it +// further; canonical h3 header order requires request-stream QPACK tapping +// which is a follow-up task). +type FPHandler struct { + logger *zap.Logger +} + +func (FPHandler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.decnet_fp", + New: func() caddy.Module { return new(FPHandler) }, + } +} + +func (h *FPHandler) Provision(ctx caddy.Context) error { + h.logger = ctx.Logger() + return nil +} + +func (h *FPHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + return nil +} + +// responseCapture captures the status code and bytes written. +type responseCapture struct { + http.ResponseWriter + status int + bytes int +} + +func (rc *responseCapture) WriteHeader(status int) { + rc.status = status + rc.ResponseWriter.WriteHeader(status) +} + +func (rc *responseCapture) Write(b []byte) (int, error) { + n, err := rc.ResponseWriter.Write(b) + rc.bytes += n + if rc.status == 0 { + rc.status = 200 + } + return n, err +} + +func (h *FPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + rc := &responseCapture{ResponseWriter: w} + err := next.ServeHTTP(rc, r) + + protoTag := "h1" + if r.ProtoMajor == 2 { + protoTag = "h2" + } else if r.ProtoMajor == 3 { + protoTag = "h3" + } + + status := rc.status + if status == 0 { + status = 200 + } + + go sendFP(map[string]interface{}{ + "kind": "access_log", + "remote_addr": r.RemoteAddr, + "method": r.Method, + "path": r.URL.Path, + "proto": r.Proto, + "proto_tag": protoTag, + "status": status, + "bytes": rc.bytes, + "ts": time.Now().UTC().Format(time.RFC3339), + }) + + // For h3, emit best-effort http_request_headers (map order, degraded). + if r.ProtoMajor == 3 { + ordered := make([][]string, 0, len(r.Header)) + var cookie, acceptLang string + for name, vals := range r.Header { + v := "" + if len(vals) > 0 { + v = vals[0] + } + ordered = append(ordered, []string{name, v}) + switch http.CanonicalHeaderKey(name) { + case "Cookie": + cookie = v + case "Accept-Language": + acceptLang = v + } + } + go sendFP(map[string]interface{}{ + "kind": "http_request_headers", + "remote_addr": r.RemoteAddr, + "proto_tag": "h3", + "method": r.Method, + "path": r.URL.Path, + "headers_ordered": ordered, + "cookie": cookie, + "accept_language": acceptLang, + "h3_order_note": "degraded_map_iteration", + "ts": time.Now().UTC().Format(time.RFC3339), + }) + } + + return err +} + +var ( + _ caddy.Provisioner = (*FPListenerWrapper)(nil) + _ caddy.ListenerWrapper = (*FPListenerWrapper)(nil) + _ caddyfile.Unmarshaler = (*FPListenerWrapper)(nil) + _ caddy.Provisioner = (*FPHandler)(nil) + _ caddyhttp.MiddlewareHandler = (*FPHandler)(nil) + _ caddyfile.Unmarshaler = (*FPHandler)(nil) +) + +// ── caddy.logging.encoders.decnet_jsonl ────────────────────────────────────── + +// DecnetJSONLEncoder is a registered Caddy module stub. A full zapcore.Encoder +// implementation (required for `log { format decnet_jsonl }`) is deferred; +// access_log records are emitted by FPHandler.ServeHTTP instead. +type DecnetJSONLEncoder struct { + logger *zap.Logger +} + +func (DecnetJSONLEncoder) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.decnet_jsonl", + New: func() caddy.Module { return new(DecnetJSONLEncoder) }, + } +} + +func (e *DecnetJSONLEncoder) Provision(ctx caddy.Context) error { + e.logger = ctx.Logger() + return nil +} diff --git a/decnet/templates/https/entrypoint.sh b/decnet/templates/https/entrypoint.sh index c162625d..6a449dc0 100644 --- a/decnet/templates/https/entrypoint.sh +++ b/decnet/templates/https/entrypoint.sh @@ -29,7 +29,8 @@ if [ ! -f "$CERT" ] || [ ! -f "$KEY" ]; then 2>/dev/null fi -# Parse HTTP_VERSIONS JSON → Caddy protocol tokens (h1 / h2 / h3) +# Parse HTTP_VERSIONS JSON → Caddy protocol tokens (h1 / h2 only). +# h3 is handled by decnet_h3 Caddy app (H3App owns UDP/443); never goes to Caddy native h3. CADDY_PROTOCOLS=$(python3 -c " import json, os versions = json.loads(os.environ.get('HTTP_VERSIONS', '[\"http/1.1\"]')) @@ -38,11 +39,17 @@ if 'http/1.1' in versions: tokens.append('h1') if 'http/2' in versions: tokens.append('h2') -if 'http/3' in versions: - tokens.append('h3') print(' '.join(tokens) if tokens else 'h1') ") +# When http/3 is selected, activate the decnet_h3 Caddy app (global block). +DECNET_H3_GLOBAL=$(python3 -c " +import json, os +versions = json.loads(os.environ.get('HTTP_VERSIONS', '[\"http/1.1\"]')) +if 'http/3' in versions: + print(' decnet_h3') +") + DECNET_FP_SOCK="${DECNET_FP_SOCK:-/run/decnet/fp.sock}" # Remove stale socket from a previous run rm -f "$DECNET_FP_SOCK" @@ -50,10 +57,11 @@ rm -f "$DECNET_FP_SOCK" cat > /etc/caddy/Caddyfile < None: def forward_syslog(line: str, log_target: str) -> None: """No-op stub. TCP forwarding is handled by rsyslog, not by service containers.""" pass + + +# ─── JA4H (local copy — containers can't import from decnet.sniffer) ───────── + + +def _sha256_12(s: str) -> str: + return _hashlib.sha256(s.encode()).hexdigest()[:12] + + +def _compute_ja4h( + method: str, + proto: str, + headers_ordered: list, + cookie: str = "", + accept_lang: str = "", +) -> str: + """Compute JA4H per the FoxIO public spec. + + headers_ordered is a list of [name, value] pairs (or bare name strings). + """ + method_tag = (method[:2].upper() if method else "UN") + ver_map = { + "HTTP/1.0": "10", "HTTP/1.1": "11", "HTTP/2.0": "20", "HTTP/3.0": "30", + "H1": "11", "H2": "20", "H3": "30", + "h1": "11", "h2": "20", "h3": "30", + } + ver_tag = ver_map.get(proto.upper(), "00") + names = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers_ordered + ] + has_cookie = "c" if any(n == "cookie" for n in names) else "n" + has_referer = "r" if any(n == "referer" for n in names) else "n" + lang_tag = (accept_lang[:4].ljust(4, "0") if accept_lang else "0000") + filtered = [n for n in names if n not in ("cookie", "referer")] + count_tag = f"{min(len(filtered), 99):02d}" + header_hash = _sha256_12(",".join(filtered)) + if cookie: + pairs = sorted(p.strip() for p in cookie.split(";") if "=" in p.strip()) + cookie_hash = _sha256_12(";".join(pairs)) + else: + cookie_hash = "000000000000" + return f"{method_tag}{ver_tag}{has_cookie}{has_referer}{lang_tag}_{count_tag}_{header_hash}_{cookie_hash}" + + +# ─── Caddy fingerprint socket reader ───────────────────────────────────────── + +_FP_BUF = 65536 + + +def _fp_socket_reader(node_name: str, service_name: str, log_target: str) -> None: + sock_path = _os.environ.get("DECNET_FP_SOCK", "/run/decnet/fp.sock") + try: + sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM) + sock.bind(sock_path) + except OSError: + return + while True: + try: + data = sock.recv(_FP_BUF) + record = _json.loads(data) + except (OSError, ValueError): + continue + kind = record.get("kind", "") + remote = record.get("remote_addr", "-") + + if kind == "h2_settings": + ln = syslog_line( + service_name, node_name, "http2_settings", SEVERITY_INFO, + remote_addr=remote, + settings=_json.dumps(record.get("settings", {})), + frame_order=_json.dumps(record.get("frame_order", [])), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "h3_settings": + ln = syslog_line( + service_name, node_name, "http3_settings", SEVERITY_INFO, + remote_addr=remote, + settings=_json.dumps(record.get("settings", {})), + frame_order=_json.dumps(record.get("frame_order", [])), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "http_request_headers": + # Canonical header order from the listener wrapper. + headers = record.get("headers_ordered", []) + method = record.get("method", "") + proto = record.get("proto_tag", "h1") + cookie = record.get("cookie", "") + accept_lang = record.get("accept_language", "") + ja4h = _compute_ja4h(method, proto, headers, cookie, accept_lang) + names_only = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers + ] + ln = syslog_line( + service_name, node_name, "http_request_fingerprint", SEVERITY_INFO, + remote_addr=remote, + proto=proto, + method=method, + path=record.get("path", ""), + ja4h=ja4h, + headers_ordered=_json.dumps(names_only), + cookie=cookie, + accept_language=accept_lang, + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "access_log": + ln = syslog_line( + service_name, node_name, "http_access", SEVERITY_INFO, + remote_addr=remote, + method=record.get("method", ""), + path=record.get("path", ""), + proto=record.get("proto_tag", "-"), + status=str(record.get("status", 0)), + bytes=str(record.get("bytes", 0)), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + +def start_fp_socket_reader(node_name: str, service_name: str, log_target: str) -> None: + t = _threading.Thread( + target=_fp_socket_reader, + args=(node_name, service_name, log_target), + daemon=True, + ) + t.start() diff --git a/decnet/templates/syslog_bridge.py b/decnet/templates/syslog_bridge.py index dbbbd90f..da339083 100644 --- a/decnet/templates/syslog_bridge.py +++ b/decnet/templates/syslog_bridge.py @@ -16,6 +16,7 @@ from __future__ import annotations import base64 import binascii +import hashlib as _hashlib import json as _json import os as _os import re @@ -264,6 +265,49 @@ def forward_syslog(line: str, log_target: str) -> None: pass +# ─── JA4H (local copy — containers can't import from decnet.sniffer) ───────── + + +def _sha256_12(s: str) -> str: + return _hashlib.sha256(s.encode()).hexdigest()[:12] + + +def _compute_ja4h( + method: str, + proto: str, + headers_ordered: list, + cookie: str = "", + accept_lang: str = "", +) -> str: + """Compute JA4H per the FoxIO public spec. + + headers_ordered is a list of [name, value] pairs (or bare name strings). + """ + method_tag = (method[:2].upper() if method else "UN") + ver_map = { + "HTTP/1.0": "10", "HTTP/1.1": "11", "HTTP/2.0": "20", "HTTP/3.0": "30", + "H1": "11", "H2": "20", "H3": "30", + "h1": "11", "h2": "20", "h3": "30", + } + ver_tag = ver_map.get(proto.upper(), "00") + names = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers_ordered + ] + has_cookie = "c" if any(n == "cookie" for n in names) else "n" + has_referer = "r" if any(n == "referer" for n in names) else "n" + lang_tag = (accept_lang[:4].ljust(4, "0") if accept_lang else "0000") + filtered = [n for n in names if n not in ("cookie", "referer")] + count_tag = f"{min(len(filtered), 99):02d}" + header_hash = _sha256_12(",".join(filtered)) + if cookie: + pairs = sorted(p.strip() for p in cookie.split(";") if "=" in p.strip()) + cookie_hash = _sha256_12(";".join(pairs)) + else: + cookie_hash = "000000000000" + return f"{method_tag}{ver_tag}{has_cookie}{has_referer}{lang_tag}_{count_tag}_{header_hash}_{cookie_hash}" + + # ─── Caddy fingerprint socket reader ───────────────────────────────────────── _FP_BUF = 65536 @@ -284,6 +328,7 @@ def _fp_socket_reader(node_name: str, service_name: str, log_target: str) -> Non continue kind = record.get("kind", "") remote = record.get("remote_addr", "-") + if kind == "h2_settings": ln = syslog_line( service_name, node_name, "http2_settings", SEVERITY_INFO, @@ -294,14 +339,54 @@ def _fp_socket_reader(node_name: str, service_name: str, log_target: str) -> Non write_syslog_file(ln) if log_target: forward_syslog(ln, log_target) - elif kind == "http_request": + + elif kind == "h3_settings": + ln = syslog_line( + service_name, node_name, "http3_settings", SEVERITY_INFO, + remote_addr=remote, + settings=_json.dumps(record.get("settings", {})), + frame_order=_json.dumps(record.get("frame_order", [])), + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "http_request_headers": + # Canonical header order from the listener wrapper. + headers = record.get("headers_ordered", []) + method = record.get("method", "") + proto = record.get("proto_tag", "h1") + cookie = record.get("cookie", "") + accept_lang = record.get("accept_language", "") + ja4h = _compute_ja4h(method, proto, headers, cookie, accept_lang) + names_only = [ + (h[0].lower() if isinstance(h, (list, tuple)) else h.lower()) + for h in headers + ] ln = syslog_line( service_name, node_name, "http_request_fingerprint", SEVERITY_INFO, remote_addr=remote, + proto=proto, + method=method, + path=record.get("path", ""), + ja4h=ja4h, + headers_ordered=_json.dumps(names_only), + cookie=cookie, + accept_language=accept_lang, + ) + write_syslog_file(ln) + if log_target: + forward_syslog(ln, log_target) + + elif kind == "access_log": + ln = syslog_line( + service_name, node_name, "http_access", SEVERITY_INFO, + remote_addr=remote, + method=record.get("method", ""), + path=record.get("path", ""), proto=record.get("proto_tag", "-"), - headers_ordered=_json.dumps(record.get("headers_ordered", [])), - cookie=record.get("cookie", ""), - accept_language=record.get("accept_language", ""), + status=str(record.get("status", 0)), + bytes=str(record.get("bytes", 0)), ) write_syslog_file(ln) if log_target: diff --git a/decnet/web/ingester.py b/decnet/web/ingester.py index bf3b8849..33ef05ed 100644 --- a/decnet/web/ingester.py +++ b/decnet/web/ingester.py @@ -637,7 +637,7 @@ async def _extract_bounty( "payload": { "fingerprint_type": "ja4h", "ja4h": _ja4h, - "protocol": _fields.get("protocol", "h1"), + "protocol": _fields.get("proto") or _fields.get("protocol", "h1"), "method": _fields.get("method"), "path": _fields.get("path"), }, @@ -1471,3 +1471,7 @@ def _classify_ua(ua: str) -> tuple[str, Optional[str], list[str]]: return "browser", None, signals return "nonstandard", None, signals + +# Test-facing alias so tests can import by a stable name. +_process_fingerprint_bounties = _extract_bounty + diff --git a/tests/services/test_https_compose_h3_app.py b/tests/services/test_https_compose_h3_app.py new file mode 100644 index 00000000..6a8e27d7 --- /dev/null +++ b/tests/services/test_https_compose_h3_app.py @@ -0,0 +1,160 @@ +""" +Verify that the HTTPS Caddyfile template (generated by entrypoint.sh): + - Includes `decnet_h3` in the global block when http/3 is selected. + - Does NOT include `h3` in the Caddy `protocols` line when http/3 is selected + (H3App owns UDP/443 directly). + - Does NOT include `decnet_h3` when http/3 is not selected. +""" +from __future__ import annotations + +import subprocess +import tempfile +import os +from pathlib import Path + +import pytest + +ENTRYPOINT = Path(__file__).parent.parent.parent / "decnet" / "templates" / "https" / "entrypoint.sh" + + +def run_entrypoint_to_caddyfile(http_versions: list[str], env_extra: dict | None = None) -> str: + """ + Run entrypoint.sh with a stub TLS cert and extract the generated Caddyfile. + Returns the Caddyfile content as a string, or raises on failure. + """ + if not ENTRYPOINT.exists(): + pytest.skip("entrypoint.sh not found") + + with tempfile.TemporaryDirectory() as tmpdir: + # Generate a throwaway self-signed cert so entrypoint doesn't fail. + cert_path = os.path.join(tmpdir, "cert.pem") + key_path = os.path.join(tmpdir, "key.pem") + subprocess.run( + ["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", key_path, "-out", cert_path, + "-days", "1", "-subj", "/CN=test"], + capture_output=True, check=True, + ) + + caddy_dir = os.path.join(tmpdir, "caddy") + os.makedirs(caddy_dir, exist_ok=True) + caddyfile_path = os.path.join(caddy_dir, "Caddyfile") + + env = { + "PATH": os.environ.get("PATH", "/usr/bin:/bin"), + "HTTP_VERSIONS": str(http_versions).replace("'", '"'), + "TLS_DIR": tmpdir, + "TLS_CERT": cert_path, + "TLS_KEY": key_path, + "NODE_NAME": "test", + "LOG_TARGET": "", + "DECNET_FP_SOCK": os.path.join(tmpdir, "fp.sock"), + } + if env_extra: + env.update(env_extra) + + # Patch entrypoint to stop after writing the Caddyfile (before Flask). + script = f""" +set -e +export TLS_DIR="{tmpdir}" +export TLS_CERT="{cert_path}" +export TLS_KEY="{key_path}" +""" + # Source just the variable-computation part of entrypoint.sh then write + # the Caddyfile. We replace `exec caddy` with `exit 0`. + src = ENTRYPOINT.read_text() + # Replace the Flask+Caddy run portion with early exit. + src = src.replace("python3 /opt/server.py &", "exit 0") + patched = script + src + + import json + http_versions_json = json.dumps(http_versions) + env["HTTP_VERSIONS"] = http_versions_json + + result = subprocess.run( + ["bash", "-c", patched], + env=env, + capture_output=True, + text=True, + timeout=10, + ) + + caddyfile_content = "" + # The Caddyfile is written to /etc/caddy/Caddyfile. + # Since we're not root in tests, override it via env trick — just + # extract the content from the script output/error instead. + # Better: redirect output of the heredoc. + # Actually: just parse the script to extract the heredoc. + import re + m = re.search(r"cat > /etc/caddy/Caddyfile < dict: + return { + "event_type": event_type, + "decky": "test-decky", + "service": "https", + "attacker_ip": "1.2.3.4", + "fields": fields, + } + + +async def _run_bounty_check(log_data: dict) -> list: + """Run the ingester's _process_log_event and collect add_bounty calls.""" + from decnet.web.ingester import _process_fingerprint_bounties + + repo = MagicMock() + repo.add_bounty = AsyncMock() + bus = MagicMock() + + await _process_fingerprint_bounties(repo, log_data, bus) + return [call.args[0] for call in repo.add_bounty.call_args_list] + + +# ------ import guard ---------------------------------------------------------- + +def _import_process(): + """Return _process_fingerprint_bounties or skip if not found.""" + try: + from decnet.web.ingester import _process_fingerprint_bounties + return _process_fingerprint_bounties + except ImportError: + pytest.skip("_process_fingerprint_bounties not yet public") + + +# ------ tests ----------------------------------------------------------------- + +class TestJA4HIngestion: + def test_new_shape_fires_bounty(self): + """New shape: ja4h field present → bounty added.""" + _import_process() + log_data = _make_log_data("http_request_fingerprint", { + "ja4h": "GE11nn0000_03_abc123def456_000000000000", + "proto": "h1", + "method": "GET", + "path": "/index.html", + "headers_ordered": json.dumps(["host", "user-agent", "accept"]), + }) + bounties = pytest.importorskip("asyncio").run(_run_bounty_check(log_data)) + ja4h_bounties = [b for b in bounties if b.get("payload", {}).get("fingerprint_type") == "ja4h"] + assert len(ja4h_bounties) == 1 + assert ja4h_bounties[0]["payload"]["ja4h"] == "GE11nn0000_03_abc123def456_000000000000" + assert ja4h_bounties[0]["payload"]["protocol"] == "h1" + + def test_old_shape_no_crash(self): + """Old shape: no ja4h field → no bounty, no exception.""" + _import_process() + log_data = _make_log_data("http_request_fingerprint", { + "proto": "h1", + "method": "GET", + "path": "/", + "headers_ordered": json.dumps(["host", "user-agent"]), + "cookie": "", + "accept_language": "", + }) + import asyncio + bounties = asyncio.run(_run_bounty_check(log_data)) + ja4h_bounties = [b for b in bounties if b.get("payload", {}).get("fingerprint_type") == "ja4h"] + assert len(ja4h_bounties) == 0 + + def test_proto_field_alias(self): + """proto (new) and protocol (old) both populate payload.protocol.""" + _import_process() + for field_name, field_val in [("proto", "h2"), ("protocol", "h2")]: + log_data = _make_log_data("http_request_fingerprint", { + "ja4h": "GE20nn0000_02_aabbccddeeff_000000000000", + field_name: field_val, + "method": "GET", + "path": "/", + }) + import asyncio + bounties = asyncio.run(_run_bounty_check(log_data)) + ja4h_bounties = [b for b in bounties if b.get("payload", {}).get("fingerprint_type") == "ja4h"] + if ja4h_bounties: + assert ja4h_bounties[0]["payload"]["protocol"] == "h2", f"field={field_name}"