mirror of https://github.com/containers/image.git
Merge pull request #2873 from Luap99/rektor
Replace sigstore/rekor/pkg/client with a manually-created client
This commit is contained in:
commit
f942e09ebe
|
|
@ -95,8 +95,13 @@ _run_image_tests() {
|
|||
msg "Setup known_hosts for root"
|
||||
ssh-keyscan localhost > /root/.ssh/known_hosts \
|
||||
|
||||
msg "Start rekor server as $ROOTLESS_USER"
|
||||
showrun ssh $ROOTLESS_USER@localhost $GOSRC/signature/sigstore/rekor/testdata/start-rekor.sh ci
|
||||
# remove rekor server on function exit
|
||||
trap "ssh $ROOTLESS_USER@localhost $GOSRC/signature/sigstore/rekor/testdata/start-rekor.sh ci remove" RETURN
|
||||
|
||||
msg "Executing tests as $ROOTLESS_USER"
|
||||
showrun ssh $ROOTLESS_USER@localhost make -C $GOSRC test "BUILDTAGS='$BUILDTAGS'" "TESTFLAGS=-v"
|
||||
showrun ssh $ROOTLESS_USER@localhost make -C $GOSRC test "BUILDTAGS='$BUILDTAGS'" "TESTFLAGS=-v" "REKOR_SERVER_URL='http://127.0.0.1:3000'"
|
||||
}
|
||||
|
||||
req_env_vars GOSRC
|
||||
|
|
|
|||
19
go.mod
19
go.mod
|
|
@ -18,7 +18,6 @@ require (
|
|||
github.com/docker/docker v28.3.0+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.9.3
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/go-openapi/strfmt v0.23.1-0.20250509134642-64a09ef0e084
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
|
|
@ -30,7 +29,6 @@ require (
|
|||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.0
|
||||
github.com/sigstore/fulcio v1.6.6
|
||||
github.com/sigstore/rekor v1.3.10
|
||||
github.com/sigstore/sigstore v1.9.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
|
|
@ -50,7 +48,6 @@ require (
|
|||
github.com/Microsoft/hcsshim v0.13.0 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
|
|
@ -70,16 +67,6 @@ require (
|
|||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
|
|
@ -88,10 +75,8 @@ require (
|
|||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
|
||||
|
|
@ -104,10 +89,8 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
|
|
@ -124,7 +107,6 @@ require (
|
|||
github.com/tchap/go-patricia/v2 v2.3.2 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
|
|
@ -132,7 +114,6 @@ require (
|
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
|
|
|
|||
44
go.sum
44
go.sum
|
|
@ -16,8 +16,6 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
|||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
|
@ -104,33 +102,11 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||
github.com/go-logr/logr v1.4.3/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-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
|
||||
github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
|
||||
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
|
||||
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
|
||||
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.1-0.20250509134642-64a09ef0e084 h1:PNIpnlKt8VYiQuxzI48nNqM3M0ZW+PPBMv/LTEQlNDo=
|
||||
github.com/go-openapi/strfmt v0.23.1-0.20250509134642-64a09ef0e084/go.mod h1:WHBPDONkZMEwENrJXFU37tIde3N8Q1lrlHSlXbF49LE=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
|
@ -181,8 +157,6 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
|
|||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
|
||||
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
|
@ -207,8 +181,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ=
|
||||
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
|
@ -250,8 +222,6 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
|
|
@ -260,8 +230,6 @@ github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU
|
|||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pkg/errors v0.8.0/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=
|
||||
|
|
@ -308,8 +276,6 @@ github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw
|
|||
github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk=
|
||||
github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc=
|
||||
github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
|
||||
github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU=
|
||||
github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A=
|
||||
github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU=
|
||||
github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
|
@ -361,8 +327,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI=
|
||||
go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
|
|
@ -385,8 +349,6 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
|
|||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
|
@ -409,8 +371,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -492,8 +452,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const rekorHashedrekordKind = "hashedrekord"
|
||||
|
||||
type RekorHashedrekord struct {
|
||||
APIVersion *string `json:"apiVersion"`
|
||||
Spec json.RawMessage `json:"spec"`
|
||||
}
|
||||
|
||||
func (m *RekorHashedrekord) Kind() string {
|
||||
return rekorHashedrekordKind
|
||||
}
|
||||
|
||||
func (m *RekorHashedrekord) SetKind(val string) {
|
||||
}
|
||||
|
||||
func (m *RekorHashedrekord) UnmarshalJSON(raw []byte) error {
|
||||
var base struct {
|
||||
Kind string `json:"kind"`
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch base.Kind {
|
||||
case rekorHashedrekordKind:
|
||||
var data struct { // We can’t use RekorHashedRekord directly, because that would be an infinite recursion.
|
||||
APIVersion *string `json:"apiVersion"`
|
||||
Spec json.RawMessage `json:"spec"`
|
||||
}
|
||||
dec = json.NewDecoder(bytes.NewReader(raw))
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&data); err != nil {
|
||||
return err
|
||||
}
|
||||
res := RekorHashedrekord{
|
||||
APIVersion: data.APIVersion,
|
||||
Spec: data.Spec,
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid kind value: %q", base.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func (m RekorHashedrekord) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Kind string `json:"kind"`
|
||||
APIVersion *string `json:"apiVersion"`
|
||||
Spec json.RawMessage `json:"spec"`
|
||||
}{
|
||||
Kind: m.Kind(),
|
||||
APIVersion: m.APIVersion,
|
||||
Spec: m.Spec,
|
||||
})
|
||||
}
|
||||
|
||||
type RekorHashedrekordV001Schema struct {
|
||||
Data *RekorHashedrekordV001SchemaData `json:"data"`
|
||||
Signature *RekorHashedrekordV001SchemaSignature `json:"signature"`
|
||||
}
|
||||
|
||||
type RekorHashedrekordV001SchemaData struct {
|
||||
Hash *RekorHashedrekordV001SchemaDataHash `json:"hash,omitempty"`
|
||||
}
|
||||
|
||||
type RekorHashedrekordV001SchemaDataHash struct {
|
||||
Algorithm *string `json:"algorithm"`
|
||||
Value *string `json:"value"`
|
||||
}
|
||||
|
||||
const (
|
||||
RekorHashedrekordV001SchemaDataHashAlgorithmSha256 string = "sha256"
|
||||
)
|
||||
|
||||
type RekorHashedrekordV001SchemaSignature struct {
|
||||
Content []byte `json:"content,omitempty"`
|
||||
PublicKey *RekorHashedrekordV001SchemaSignaturePublicKey `json:"publicKey,omitempty"`
|
||||
}
|
||||
|
||||
type RekorHashedrekordV001SchemaSignaturePublicKey struct {
|
||||
Content []byte `json:"content,omitempty"`
|
||||
}
|
||||
|
|
@ -12,12 +12,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
|
||||
"github.com/sigstore/rekor/pkg/generated/models"
|
||||
)
|
||||
|
||||
// This is the github.com/sigstore/rekor/pkg/generated/models.Hashedrekord.APIVersion for github.com/sigstore/rekor/pkg/generated/models.HashedrekordV001Schema.
|
||||
// We could alternatively use github.com/sigstore/rekor/pkg/types/hashedrekord.APIVERSION, but that subpackage adds too many dependencies.
|
||||
const HashedRekordV001APIVersion = "0.0.1"
|
||||
const RekorHashedRekordV001APIVersion = "0.0.1"
|
||||
|
||||
// UntrustedRekorSET is a parsed content of the sigstore-signature Rekor SET
|
||||
// (note that this a signature-specific format, not a format directly used by the Rekor API).
|
||||
|
|
@ -135,31 +134,20 @@ func VerifyRekorSET(publicKeys []*ecdsa.PublicKey, unverifiedRekorSET []byte, un
|
|||
if err := json.Unmarshal(untrustedSETPayloadCanonicalBytes, &rekorPayload); err != nil {
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("parsing Rekor SET payload: %v", err.Error()))
|
||||
}
|
||||
// FIXME: Use a different decoder implementation? The Swagger-generated code is kinda ridiculous, with the need to re-marshal
|
||||
// hashedRekor.Spec and so on.
|
||||
// Especially if we anticipate needing to decode different data formats…
|
||||
// That would also allow being much more strict about JSON.
|
||||
//
|
||||
// Alternatively, rely on the existing .Validate() methods instead of manually checking for nil all over the place.
|
||||
var hashedRekord models.Hashedrekord
|
||||
// FIXME: Consider being much more strict about decoding JSON.
|
||||
var hashedRekord RekorHashedrekord
|
||||
if err := json.Unmarshal(rekorPayload.Body, &hashedRekord); err != nil {
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("decoding the body of a Rekor SET payload: %v", err))
|
||||
}
|
||||
// The decode of models.HashedRekord validates the "kind": "hashedrecord" field, which is otherwise invisible to us.
|
||||
// The decode of HashedRekord validates the "kind": "hashedrecord" field, which is otherwise invisible to us.
|
||||
if hashedRekord.APIVersion == nil {
|
||||
return time.Time{}, NewInvalidSignatureError("missing Rekor SET Payload API version")
|
||||
}
|
||||
if *hashedRekord.APIVersion != HashedRekordV001APIVersion {
|
||||
if *hashedRekord.APIVersion != RekorHashedRekordV001APIVersion {
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("unsupported Rekor SET Payload hashedrekord version %#v", hashedRekord.APIVersion))
|
||||
}
|
||||
hashedRekordV001Bytes, err := json.Marshal(hashedRekord.Spec)
|
||||
if err != nil {
|
||||
// Coverage: hashedRekord.Spec is an any that was just unmarshaled,
|
||||
// so this should never fail.
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("re-creating hashedrekord spec: %v", err))
|
||||
}
|
||||
var hashedRekordV001 models.HashedrekordV001Schema
|
||||
if err := json.Unmarshal(hashedRekordV001Bytes, &hashedRekordV001); err != nil {
|
||||
var hashedRekordV001 RekorHashedrekordV001Schema
|
||||
if err := json.Unmarshal(hashedRekord.Spec, &hashedRekordV001); err != nil {
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf("decoding hashedrekod spec: %v", err))
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +203,7 @@ func VerifyRekorSET(publicKeys []*ecdsa.PublicKey, unverifiedRekorSET []byte, un
|
|||
// Eventually we should support them as well.
|
||||
// Short-term, Cosign (as of 2024-02 and Cosign 2.2.3) only produces and accepts SHA-256, so right now that’s not a compatibility
|
||||
// issue.
|
||||
if *hashedRekordV001.Data.Hash.Algorithm != models.HashedrekordV001SchemaDataHashAlgorithmSha256 {
|
||||
if *hashedRekordV001.Data.Hash.Algorithm != RekorHashedrekordV001SchemaDataHashAlgorithmSha256 {
|
||||
return time.Time{}, NewInvalidSignatureError(fmt.Sprintf(`Unexpected "data.hash.algorithm" value %#v`, *hashedRekordV001.Data.Hash.Algorithm))
|
||||
}
|
||||
if hashedRekordV001.Data.Hash.Value == nil {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/sigstore/rekor/pkg/generated/models"
|
||||
"github.com/sigstore/sigstore/pkg/cryptoutils"
|
||||
sigstoreSignature "github.com/sigstore/sigstore/pkg/signature"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -265,22 +263,24 @@ func TestVerifyRekorSET(t *testing.T) {
|
|||
cosignPayloadSHA256 := sha256.Sum256(cosignPayloadBytes)
|
||||
cosignSigBytes, err := base64.StdEncoding.DecodeString(string(cosignSigBase64))
|
||||
require.NoError(t, err)
|
||||
validHashedRekord := models.Hashedrekord{
|
||||
APIVersion: stringPtr(HashedRekordV001APIVersion),
|
||||
Spec: models.HashedrekordV001Schema{
|
||||
Data: &models.HashedrekordV001SchemaData{
|
||||
Hash: &models.HashedrekordV001SchemaDataHash{
|
||||
Algorithm: stringPtr(models.HashedrekordV001SchemaDataHashAlgorithmSha256),
|
||||
Value: stringPtr(hex.EncodeToString(cosignPayloadSHA256[:])),
|
||||
},
|
||||
},
|
||||
Signature: &models.HashedrekordV001SchemaSignature{
|
||||
Content: strfmt.Base64(cosignSigBytes),
|
||||
PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{
|
||||
Content: strfmt.Base64(cosignCertBytes),
|
||||
},
|
||||
validHashedRekordSpec, err := json.Marshal(RekorHashedrekordV001Schema{
|
||||
Data: &RekorHashedrekordV001SchemaData{
|
||||
Hash: &RekorHashedrekordV001SchemaDataHash{
|
||||
Algorithm: stringPtr(RekorHashedrekordV001SchemaDataHashAlgorithmSha256),
|
||||
Value: stringPtr(hex.EncodeToString(cosignPayloadSHA256[:])),
|
||||
},
|
||||
},
|
||||
Signature: &RekorHashedrekordV001SchemaSignature{
|
||||
Content: cosignSigBytes,
|
||||
PublicKey: &RekorHashedrekordV001SchemaSignaturePublicKey{
|
||||
Content: cosignCertBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
validHashedRekord := RekorHashedrekord{
|
||||
APIVersion: stringPtr(RekorHashedRekordV001APIVersion),
|
||||
Spec: validHashedRekordSpec,
|
||||
}
|
||||
validHashedRekordJSON, err := json.Marshal(validHashedRekord)
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package rekor
|
||||
|
||||
// The following code is the essence of the relevant code paths from github.com/go-openapi/runtime,
|
||||
// heavily modified since.
|
||||
|
||||
// Copyright 2015 go-swagger maintainers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// makeRequest makes a http request to the requested requestPath, and returns the received response.
|
||||
func (r *rekorClient) makeRequest(ctx context.Context, method, requestPath string, bodyContent any) (*http.Response, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var body io.Reader
|
||||
headers := http.Header{}
|
||||
|
||||
headers.Set("Accept", "application/json")
|
||||
if bodyContent != nil {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
body = buf
|
||||
headers.Set("Content-Type", "application/json")
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(bodyContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, path.Join(r.basePath, requestPath), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only Scheme and Host are used from rekorURL.
|
||||
// Really this should probabbly use r.rekorURL.JoinPath(requestPath) (which, notably, correctly deals with path escaping),
|
||||
// and pass that to NewRequestWithContext, but this use of path.Join is consistent with go-openapi/runtime v0.24.1 .
|
||||
req.URL.Scheme = r.rekorURL.Scheme
|
||||
req.URL.Host = r.rekorURL.Host
|
||||
req.Header = headers
|
||||
|
||||
res, err := r.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note that we don’t care to even read the Content-Type: header; we blindly assume the format is the requested JSON.
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// decodeHTTPResponseBodyAsJSON decodes the body of a HTTP response in a manner compatible with github.com/go-openapi/runtime.
|
||||
func decodeHTTPResponseBodyAsJSON(res *http.Response, data any) error {
|
||||
dec := json.NewDecoder(res.Body)
|
||||
dec.UseNumber()
|
||||
err := dec.Decode(data)
|
||||
if err == io.EOF {
|
||||
// This seems unwanted at a first glance; go-swagger added it in https://github.com/go-swagger/go-swagger/issues/192 , it’s unclear
|
||||
// whether it’s correct or still necessary.
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -8,44 +8,57 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
signerInternal "github.com/containers/image/v5/signature/sigstore/internal"
|
||||
"github.com/go-openapi/strfmt"
|
||||
rekor "github.com/sigstore/rekor/pkg/client"
|
||||
"github.com/sigstore/rekor/pkg/generated/client"
|
||||
"github.com/sigstore/rekor/pkg/generated/client/entries"
|
||||
"github.com/sigstore/rekor/pkg/generated/models"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultRetryCount is the default number of retries
|
||||
defaultRetryCount = 3
|
||||
)
|
||||
|
||||
// WithRekor asks the generated signature to be uploaded to the specified Rekor server,
|
||||
// and to include a log inclusion proof in the signature.
|
||||
func WithRekor(rekorURL *url.URL) signerInternal.Option {
|
||||
return func(s *signerInternal.SigstoreSigner) error {
|
||||
logrus.Debugf("Using Rekor server at %s", rekorURL.Redacted())
|
||||
client, err := rekor.GetRekorClient(rekorURL.String(),
|
||||
rekor.WithLogger(leveledLoggerForLogrus(logrus.StandardLogger())))
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating Rekor client: %w", err)
|
||||
}
|
||||
u := uploader{
|
||||
client: client,
|
||||
}
|
||||
s.RekorUploader = u.uploadKeyOrCert
|
||||
client := newRekorClient(rekorURL)
|
||||
s.RekorUploader = client.uploadKeyOrCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// uploader wraps a Rekor client, basically so that we can set RekorUploader to a method instead of an one-off closure.
|
||||
type uploader struct {
|
||||
client *client.Rekor
|
||||
// rekorClient allows uploading entries to Rekor.
|
||||
type rekorClient struct {
|
||||
rekorURL *url.URL // Only Scheme and Host is actually used, consistent with github.com/sigstore/rekor/pkg/client.
|
||||
basePath string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// newRekorClient creates a rekorClient for rekorURL.
|
||||
func newRekorClient(rekorURL *url.URL) *rekorClient {
|
||||
retryableClient := retryablehttp.NewClient()
|
||||
retryableClient.RetryMax = defaultRetryCount
|
||||
retryableClient.Logger = leveledLoggerForLogrus(logrus.StandardLogger())
|
||||
basePath := rekorURL.Path
|
||||
if !strings.HasPrefix(basePath, "/") { // Includes basePath == "", i.e. URL just a https://hostname
|
||||
basePath = "/" + basePath
|
||||
}
|
||||
return &rekorClient{
|
||||
rekorURL: rekorURL,
|
||||
basePath: basePath,
|
||||
httpClient: retryableClient.StandardClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// rekorEntryToSET converts a Rekor log entry into a sigstore “signed entry timestamp”.
|
||||
func rekorEntryToSET(entry *models.LogEntryAnon) (internal.UntrustedRekorSET, error) {
|
||||
func rekorEntryToSET(entry *rekorLogEntryAnon) (internal.UntrustedRekorSET, error) {
|
||||
// We could plausibly call entry.Validate() here; that mostly just uses unnecessary reflection instead of direct == nil checks.
|
||||
// Right now the only extra validation .Validate() does is *entry.LogIndex >= 0 and a regex check on *entry.LogID;
|
||||
// we don’t particularly care about either of these (notably signature verification only uses the Body value).
|
||||
|
|
@ -77,67 +90,67 @@ func rekorEntryToSET(entry *models.LogEntryAnon) (internal.UntrustedRekorSET, er
|
|||
}
|
||||
|
||||
// uploadEntry ensures proposedEntry exists in Rekor (usually uploading it), and returns the resulting log entry.
|
||||
func (u *uploader) uploadEntry(ctx context.Context, proposedEntry models.ProposedEntry) (models.LogEntry, error) {
|
||||
params := entries.NewCreateLogEntryParamsWithContext(ctx)
|
||||
params.SetProposedEntry(proposedEntry)
|
||||
func (r *rekorClient) uploadEntry(ctx context.Context, proposedEntry rekorProposedEntry) (rekorLogEntry, error) {
|
||||
logrus.Debugf("Calling Rekor's CreateLogEntry")
|
||||
resp, err := u.client.Entries.CreateLogEntry(params)
|
||||
resp, err := r.createLogEntry(ctx, proposedEntry)
|
||||
if err != nil {
|
||||
// In ordinary operation, we should not get duplicate entries, because our payload contains a timestamp,
|
||||
// so it is supposed to be unique; and the default key format, ECDSA p256, also contains a nonce.
|
||||
// But conflicts can fairly easily happen during debugging and experimentation, so it pays to handle this.
|
||||
var conflictErr *entries.CreateLogEntryConflict
|
||||
if errors.As(err, &conflictErr) && conflictErr.Location != "" {
|
||||
location := conflictErr.Location.String()
|
||||
var conflictErr *createLogEntryConflictError
|
||||
if errors.As(err, &conflictErr) && conflictErr.location != "" {
|
||||
location := conflictErr.location
|
||||
logrus.Debugf("CreateLogEntry reported a conflict, location = %s", location)
|
||||
// We might be able to just GET the returned Location, but let’s use the generated API client.
|
||||
// We might be able to just GET the returned Location, but let’s use the formal API method.
|
||||
// OTOH that requires us to hard-code the URI structure…
|
||||
uuidDelimiter := strings.LastIndexByte(location, '/')
|
||||
if uuidDelimiter != -1 { // Otherwise the URI is unexpected, and fall through to the bottom
|
||||
uuid := location[uuidDelimiter+1:]
|
||||
logrus.Debugf("Calling Rekor's NewGetLogEntryByUUIDParamsWithContext")
|
||||
params2 := entries.NewGetLogEntryByUUIDParamsWithContext(ctx)
|
||||
params2.SetEntryUUID(uuid)
|
||||
resp2, err := u.client.Entries.GetLogEntryByUUID(params2)
|
||||
resp2, err := r.getLogEntryByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error re-loading previously-created log entry with UUID %s: %w", uuid, err)
|
||||
}
|
||||
return resp2.GetPayload(), nil
|
||||
return resp2, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Error uploading a log entry: %w", err)
|
||||
}
|
||||
return resp.GetPayload(), nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// stringPtr returns a pointer to the provided string value.
|
||||
func stringPtr(s string) *string {
|
||||
// stringPointer is a helper to create *string fields in JSON data.
|
||||
func stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// uploadKeyOrCert integrates this code into sigstore/internal.Signer.
|
||||
// Given components of the created signature, it returns a SET that should be added to the signature.
|
||||
func (u *uploader) uploadKeyOrCert(ctx context.Context, keyOrCertBytes []byte, signatureBytes []byte, payloadBytes []byte) ([]byte, error) {
|
||||
func (r *rekorClient) uploadKeyOrCert(ctx context.Context, keyOrCertBytes []byte, signatureBytes []byte, payloadBytes []byte) ([]byte, error) {
|
||||
payloadHash := sha256.Sum256(payloadBytes) // HashedRecord only accepts SHA-256
|
||||
proposedEntry := models.Hashedrekord{
|
||||
APIVersion: stringPtr(internal.HashedRekordV001APIVersion),
|
||||
Spec: models.HashedrekordV001Schema{
|
||||
Data: &models.HashedrekordV001SchemaData{
|
||||
Hash: &models.HashedrekordV001SchemaDataHash{
|
||||
Algorithm: stringPtr(models.HashedrekordV001SchemaDataHashAlgorithmSha256),
|
||||
Value: stringPtr(hex.EncodeToString(payloadHash[:])),
|
||||
},
|
||||
},
|
||||
Signature: &models.HashedrekordV001SchemaSignature{
|
||||
Content: strfmt.Base64(signatureBytes),
|
||||
PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{
|
||||
Content: strfmt.Base64(keyOrCertBytes),
|
||||
},
|
||||
hashedRekordSpec, err := json.Marshal(internal.RekorHashedrekordV001Schema{
|
||||
Data: &internal.RekorHashedrekordV001SchemaData{
|
||||
Hash: &internal.RekorHashedrekordV001SchemaDataHash{
|
||||
Algorithm: stringPointer(internal.RekorHashedrekordV001SchemaDataHashAlgorithmSha256),
|
||||
Value: stringPointer(hex.EncodeToString(payloadHash[:])),
|
||||
},
|
||||
},
|
||||
Signature: &internal.RekorHashedrekordV001SchemaSignature{
|
||||
Content: signatureBytes,
|
||||
PublicKey: &internal.RekorHashedrekordV001SchemaSignaturePublicKey{
|
||||
Content: keyOrCertBytes,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proposedEntry := internal.RekorHashedrekord{
|
||||
APIVersion: stringPointer(internal.RekorHashedRekordV001APIVersion),
|
||||
Spec: hashedRekordSpec,
|
||||
}
|
||||
|
||||
uploadedPayload, err := u.uploadEntry(ctx, &proposedEntry)
|
||||
uploadedPayload, err := r.uploadEntry(ctx, &proposedEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -145,7 +158,7 @@ func (u *uploader) uploadKeyOrCert(ctx context.Context, keyOrCertBytes []byte, s
|
|||
if len(uploadedPayload) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 Rekor entry, got %d", len(uploadedPayload))
|
||||
}
|
||||
var storedEntry *models.LogEntryAnon
|
||||
var storedEntry *rekorLogEntryAnon
|
||||
// This “loop” extracts the single value from the uploadedPayload map.
|
||||
for _, p := range uploadedPayload {
|
||||
storedEntry = &p
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package rekor
|
||||
|
||||
// The following code is the essence of the relevant code paths from github.com/sigstore/rekor/pkg/generated/client/...,
|
||||
// heavily modified since.
|
||||
|
||||
// Copyright 2021 The Sigstore Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// createLogEntryConflictError describes a response with status code 409:
|
||||
// The request conflicts with the current state of the transparency log.
|
||||
// This typically happens when trying to upload an existing entry again.
|
||||
type createLogEntryConflictError struct {
|
||||
location string
|
||||
err string
|
||||
}
|
||||
|
||||
func (o *createLogEntryConflictError) Error() string {
|
||||
return o.err
|
||||
}
|
||||
|
||||
// createLogEntry creates an entry in the transparency log
|
||||
//
|
||||
// Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified.
|
||||
func (r *rekorClient) createLogEntry(ctx context.Context, proposedEntry rekorProposedEntry) (rekorLogEntry, error) {
|
||||
res, err := r.makeRequest(ctx, http.MethodPost, "/api/v1/log/entries", proposedEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusCreated:
|
||||
result := rekorLogEntry{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case http.StatusBadRequest:
|
||||
result := rekorError{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("Rekor /api/v1/log/entries failed: bad request (%d), %+v", res.StatusCode, result)
|
||||
|
||||
case http.StatusConflict:
|
||||
result := rekorError{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, &createLogEntryConflictError{
|
||||
location: res.Header.Get("Location"),
|
||||
err: fmt.Sprintf("Rekor /api/v1/log/entries failed with a conflict (%d), %+v", res.StatusCode, result),
|
||||
}
|
||||
|
||||
default:
|
||||
result := rekorError{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("Rekor /api/v1/log/entries failed with unexpected status %d: %+v", res.StatusCode, result)
|
||||
}
|
||||
}
|
||||
|
||||
// getLogEntryByUUID gets log entry and information required to generate an inclusion proof for the entry in the transparency log
|
||||
//
|
||||
// Returns the entry, root hash, tree size, and a list of hashes that can be used to calculate proof of an entry being included in the transparency log
|
||||
func (r *rekorClient) getLogEntryByUUID(ctx context.Context, entryUUID string) (rekorLogEntry, error) {
|
||||
res, err := r.makeRequest(ctx, http.MethodGet, "/api/v1/log/entries/"+url.PathEscape(entryUUID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
result := rekorLogEntry{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case http.StatusNotFound: // We don’t care to define a separate error type; we don’t need it ourselves.
|
||||
return nil, fmt.Errorf("Rekor /api/v1/log/entries/{entryUUID}: entry not found (%d)", res.StatusCode)
|
||||
|
||||
default:
|
||||
result := rekorError{}
|
||||
if err := decodeHTTPResponseBodyAsJSON(res, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("Rekor /api/v1/log/entries/{entryUUID} failed with unexpected status %d: %+v", res.StatusCode, result)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package rekor
|
||||
|
||||
type rekorError struct {
|
||||
Code int64 `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type rekorProposedEntry interface {
|
||||
// Actually the code, currently, accepts anything that can be marshaled into JSON; use at least the Kind marker from
|
||||
// shared between RekorHashedrekord / and other accepted formats for minimal sanity checking (but without hard-coding
|
||||
// RekorHashedRekord in particular).
|
||||
|
||||
Kind() string
|
||||
SetKind(string)
|
||||
}
|
||||
|
||||
type rekorLogEntryAnon struct {
|
||||
Attestation *rekorLogEntryAnonAttestation `json:"attestation,omitempty"`
|
||||
Body any `json:"body"`
|
||||
IntegratedTime *int64 `json:"integratedTime"`
|
||||
LogID *string `json:"logID"`
|
||||
LogIndex *int64 `json:"logIndex"`
|
||||
Verification *rekorLogEntryAnonVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
type rekorLogEntryAnonAttestation struct {
|
||||
Data []byte `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type rekorLogEntryAnonVerification struct {
|
||||
InclusionProof *rekorInclusionProof `json:"inclusionProof,omitempty"`
|
||||
SignedEntryTimestamp []byte `json:"signedEntryTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
type rekorLogEntry map[string]rekorLogEntryAnon
|
||||
|
||||
type rekorInclusionProof struct {
|
||||
Checkpoint *string `json:"checkpoint"`
|
||||
Hashes []string `json:"hashes"`
|
||||
LogIndex *int64 `json:"logIndex"`
|
||||
RootHash *string `json:"rootHash"`
|
||||
TreeSize *int64 `json:"treeSize"`
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package rekor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/signature/internal"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/sigstore/sigstore/pkg/cryptoutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_rekorUploadKeyOrCert(t *testing.T) {
|
||||
REKOR_SERVER := os.Getenv("REKOR_SERVER_URL")
|
||||
if REKOR_SERVER == "" {
|
||||
t.Skip("REKOR_SERVER_URL not set or empty. This test requires a proper rekor server to run against, use signature/sigstore/rekor/scripts/start-rekor.sh to set one up quickly")
|
||||
}
|
||||
|
||||
cosignCertBytes, err := os.ReadFile("../../internal/testdata/rekor-cert")
|
||||
require.NoError(t, err)
|
||||
cosignSigBase64, err := os.ReadFile("../../internal/testdata/rekor-sig")
|
||||
require.NoError(t, err)
|
||||
cosignPayloadBytes, err := os.ReadFile("../../internal/testdata/rekor-payload")
|
||||
require.NoError(t, err)
|
||||
|
||||
// server needs a moment to set to retry a bit
|
||||
resp, err := retryablehttp.Get(REKOR_SERVER + "/api/v1/log/publicKey")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "unexpected status code from rekor server")
|
||||
|
||||
rekorPubKeyPEM, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
resp.Body.Close()
|
||||
|
||||
rekorKey, err := cryptoutils.UnmarshalPEMToPublicKey(rekorPubKeyPEM)
|
||||
require.NoError(t, err)
|
||||
rekorKeyECDSA, ok := rekorKey.(*ecdsa.PublicKey)
|
||||
require.True(t, ok)
|
||||
rekorKeysECDSA := []*ecdsa.PublicKey{rekorKeyECDSA}
|
||||
|
||||
type args struct {
|
||||
keyOrCertBytes []byte
|
||||
signatureBase64 string
|
||||
payloadBytes []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "upload valid signature",
|
||||
args: args{
|
||||
keyOrCertBytes: cosignCertBytes,
|
||||
signatureBase64: string(cosignSigBase64),
|
||||
payloadBytes: cosignPayloadBytes,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid key",
|
||||
args: args{
|
||||
keyOrCertBytes: []byte{1, 2, 3, 4},
|
||||
signatureBase64: string(cosignSigBase64),
|
||||
payloadBytes: cosignPayloadBytes,
|
||||
},
|
||||
wantErr: "Rekor /api/v1/log/entries failed: bad request (400), {Code:400 Message:error processing entry: invalid public key: failure decoding PEM}",
|
||||
},
|
||||
{
|
||||
name: "invalid signature",
|
||||
args: args{
|
||||
keyOrCertBytes: cosignCertBytes,
|
||||
signatureBase64: "AAAA" + string(cosignSigBase64),
|
||||
payloadBytes: cosignPayloadBytes,
|
||||
},
|
||||
wantErr: "Rekor /api/v1/log/entries failed: bad request (400), {Code:400 Message:error processing entry: verifying signature: ecdsa: Invalid IEEE_P1363 encoded bytes}",
|
||||
},
|
||||
{
|
||||
name: "invalid payload",
|
||||
args: args{
|
||||
keyOrCertBytes: cosignCertBytes,
|
||||
signatureBase64: string(cosignSigBase64),
|
||||
payloadBytes: []byte{2, 3, 4},
|
||||
},
|
||||
wantErr: "Rekor /api/v1/log/entries failed: bad request (400), {Code:400 Message:error processing entry: verifying signature: invalid signature when validating ASN.1 encoded signature}",
|
||||
},
|
||||
}
|
||||
u, err := url.Parse(REKOR_SERVER)
|
||||
require.NoError(t, err)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cl := newRekorClient(u)
|
||||
|
||||
signatureBytes, err := base64.StdEncoding.DecodeString(tt.args.signatureBase64)
|
||||
require.NoError(t, err)
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
// with go 1.24 this should use t.Context()
|
||||
got, err := cl.uploadKeyOrCert(context.Background(), tt.args.keyOrCertBytes, signatureBytes, tt.args.payloadBytes)
|
||||
if tt.wantErr != "" {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now verify the returned rekor set
|
||||
tm, err := internal.VerifyRekorSET(rekorKeysECDSA, got, tt.args.keyOrCertBytes, tt.args.signatureBase64, tt.args.payloadBytes)
|
||||
require.NoError(t, err)
|
||||
// Check that the returned timestamp makes sense.
|
||||
// Note that using time.After()/Before() to match will yield incorrect result.
|
||||
// time.Now() has nanosecond precision while the rekor time is constructed of the unix seconds.
|
||||
// That is why we can only compare the full unix seconds here.
|
||||
assert.GreaterOrEqual(t, tm.Unix(), currentTime.Unix(), "time: %s after rekor time: %s", currentTime, tm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Script to run a local rekor server, setup based on
|
||||
# https://github.com/sigstore/rekor/blob/main/docker-compose.yml
|
||||
# Required for Test_rekorUploadKeyOrCert unit test so it can run
|
||||
# against a real server.
|
||||
# set REKOR_SERVER_URL="http://127.0.0.1:3000"
|
||||
|
||||
set -e
|
||||
|
||||
SUFFIX=${1:-default}
|
||||
|
||||
REKOR_IMAGE=ghcr.io/sigstore/rekor/rekor-server:v1.3.10
|
||||
TRILLIAN_SIGNER_IMAGE=ghcr.io/sigstore/rekor/trillian_log_signer:v1.3.4
|
||||
TRILLIAN_SERVER_IMAGE=ghcr.io/sigstore/rekor/trillian_log_server:v1.3.4
|
||||
MYSQL_IMAGE=gcr.io/trillian-opensource-ci/db_server:v1.7.2
|
||||
|
||||
POD_NAME=rekor-pod-$SUFFIX
|
||||
|
||||
if [[ "$2" == "remove" ]]; then
|
||||
podman pod rm -f -t0 $POD_NAME
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# On errors make sure to remove the pod again.
|
||||
trap "podman pod rm -f -t0 $POD_NAME" ERR
|
||||
|
||||
podman pod create --name $POD_NAME -p 3000:3000
|
||||
|
||||
podman run -d --pod $POD_NAME --name rekor-db-$SUFFIX \
|
||||
-e MYSQL_ROOT_PASSWORD=zaphod \
|
||||
-e MYSQL_DATABASE=test \
|
||||
-e MYSQL_USER=test \
|
||||
-e MYSQL_PASSWORD=zaphod \
|
||||
$MYSQL_IMAGE
|
||||
|
||||
# The db takes a bit to start up, wait until it is ready otherwise the trillian
|
||||
# containers fail to start due the missing db connection.
|
||||
max_retries=20
|
||||
retries=0
|
||||
while [[ $retries -le $max_retries ]]; do
|
||||
out=$(podman logs rekor-db-$SUFFIX 2>&1)
|
||||
if [[ "$out" =~ "port: 3306" ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
retries=$((retries + 1))
|
||||
if [[ $retries -ge $max_retries ]]; then
|
||||
echo "Failed to wait for the database to become ready"
|
||||
podman pod rm -f -t0 $POD_NAME
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
podman run -d --pod $POD_NAME --name rekor-trillian-server-$SUFFIX \
|
||||
$TRILLIAN_SERVER_IMAGE \
|
||||
--quota_system=noop \
|
||||
--storage_system=mysql \
|
||||
--mysql_uri="test:zaphod@tcp(127.0.0.1:3306)/test" \
|
||||
--rpc_endpoint=0.0.0.0:8090 \
|
||||
--http_endpoint=0.0.0.0:8091 \
|
||||
--alsologtostderr
|
||||
|
||||
podman run -d --pod $POD_NAME --name rekor-trillian-signer-$SUFFIX \
|
||||
$TRILLIAN_SIGNER_IMAGE \
|
||||
--quota_system=noop \
|
||||
--storage_system=mysql \
|
||||
--mysql_uri="test:zaphod@tcp(127.0.0.1:3306)/test" \
|
||||
--rpc_endpoint=0.0.0.0:8190 \
|
||||
--http_endpoint=0.0.0.0:8191 \
|
||||
--force_master \
|
||||
--alsologtostderr
|
||||
|
||||
podman run -d --pod $POD_NAME --name rekor-server-$SUFFIX \
|
||||
-e TMPDIR=/var/run/attestations \
|
||||
-v "/var/run/attestations" \
|
||||
$REKOR_IMAGE \
|
||||
serve \
|
||||
--trillian_log_server.address=127.0.0.1 \
|
||||
--trillian_log_server.port=8090 \
|
||||
--rekor_server.address=0.0.0.0 \
|
||||
--rekor_server.signer=memory \
|
||||
--enable_retrieve_api=false \
|
||||
--enable_attestation_storage \
|
||||
--attestation_storage_bucket="file:///var/run/attestations" \
|
||||
--enable_stable_checkpoint \
|
||||
--search_index.storage_provider=mysql \
|
||||
--search_index.mysql.dsn="test:zaphod@tcp(127.0.0.1:3306)/test"
|
||||
Loading…
Reference in New Issue