aptly-dev / aptly

aptly - Debian repository management tool
https://www.aptly.info/
MIT License
2.54k stars 369 forks source link

Publish API: "json: cannot unmarshal array into Go struct field .Signing of type api.SigningOptions" #1266

Open mkasimd opened 2 months ago

mkasimd commented 2 months ago

When using an encrypted PGP key and delivering the passphrase, publishing fails with HTTP status code 400 and the response body {"error":"json: cannot unmarshal array into Go struct field .Signing of type api.SigningOptions"}

Detailed Description

When using a GPG key that was generated with a passphrase, publishing a repo via API fails (cropped output):

$ curl -X POST -H 'Content-Type: application/json' --data '{"SourceKind": "local", "Sources": [{"Name": "test-repo"}], "Architectures": ["i386", "amd64"], "Signing": [{"Skip": false, "Passphrase": "123456"}], "Distribution": "bookworm"}' http://localhost:8080/api/publish/:. -v
* Connected to localhost (127.0.0.1) port 8080
> POST /api/publish/:. HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.6.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 176
> 
< HTTP/1.1 400 Bad Request
< Date: Thu, 11 Apr 2024 14:08:36 GMT
< Content-Length: 97
< Content-Type: text/plain; charset=utf-8
< 
* Connection #0 to host localhost left intact
{"error":"json: cannot unmarshal array into Go struct field .Signing of type api.SigningOptions"}

This seems to be related to: json - cannot unmarshal array into Go struct when decoding array| StackOverflow

Context

Storing private keys in an encrypted manner is a security feature, I'd like to keep. As the aptly API can be made accessible TLS-encrypted behind a reverse proxy, delivering the passphrase in cleartext to the aptly API endpoint isn't a security risk either.

Possible Implementation

In api/publish.go:102, changing Signing SigningOptions to Signing []SigningOptions could solve it, if it's related to above mentioned StackOverflow issue (I haven't tested it)

Your Environment

Docker with Dockerfile (excerpt):

FROM debian:bookworm

RUN apt-get update && \
    apt-get install -y\
      ca-certificates \
      gnupg2 curl procps \
      graphviz && \
    curl -fSSL https://www.aptly.info/pubkey.txt | gpg --dearmor -o /usr/share/keyrings/aptly.gpg && \
    echo "deb [arch=amd64,arm64 signed-by=/usr/share/keyrings/aptly.gpg] http://repo.aptly.info/ squeeze main" | tee /etc/apt/sources.list.d/aptly.list && \ 
    apt-get update && apt-get install -y aptly
$ curl http://localhost:8080/api/version                                                                                                                                 ✔ 
{"Version":"1.5.0+ds1-1+b4"}
neolynx commented 2 months ago

what is the reason for adding the array ?

in our api calls we just pass a dict: $ curl -X POST -H 'Content-Type: application/json' --data '{"SourceKind": "local", "Sources": [{"Name": "test-repo"}], "Architectures": ["i386", "amd64"], "Signing": {"Skip": false, "Passphrase": "123456"}, "Distribution": "bookworm"}' http://localhost:8080/api/publish/:. -v

could you try this ?

mkasimd commented 2 months ago

That one didn't work for me either unfortunately. Up until now, I have succeeded with unencrypted PGP keys. It's also unusual that it returns 202 Accepted for almost any input. So I'd have to list items through a GET request to see if it was actually successful or not

$ curl http://localhost:8080/api/repos
[{"Name":"test-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}]

$ curl -X POST -H 'Content-Type: application/json' --data '{"SourceKind": "local", "Sources": [{"Name": "test-repo"}], "Architectures": ["i386", "amd64"], "Signing": {"Skip": false, "Passphrase": "hello"}, "Distribution": "bookworm"}' http://localhost:8080/api/publish/:. -v
* Connected to localhost (127.0.0.1) port 8080
> POST /api/publish/:. HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.6.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 174
> 
< HTTP/1.1 202 Accepted
< Content-Type: application/json; charset=utf-8
< Date: Thu, 18 Apr 2024 07:28:52 GMT
< Content-Length: 52
< 
* Connection #0 to host localhost left intact
{"Name":"Publish local: test-repo","ID":1,"State":0}

$ curl http://localhost:8080/api/publish 
[]

In the Logs of aptly:

[GIN] 2024/04/18 - 07:28:52 | 202 |    9.624038ms |      172.17.0.1 | POST     "/api/publish/:."
Signing file 'Release' with gpg, please enter your passphrase when prompted:
gpg: signing failed: No such file or directory
gpg: signing failed: No such file or directory
neolynx commented 2 months ago

looks like the keyring is not found. where is it stored ?

we are using something similar to this:

curl -fsS -X PUT -H 'Content-Type: application/json' --data \
        '{"AcquireByHash": true, "Snapshots": [{"Component": "main", "Name": "snapshot1"}],
          "Signing": {"Batch": true, "Keyring": "trustedkeys.gpg", "Passphrase": "hello"}}' \
        http://localhost:8080/api/publish/pub1

this uses ~/.gnupg/trustedkeys.gpg, also the batch argument is important for running gpg.