aptly icon indicating copy to clipboard operation
aptly copied to clipboard

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

Open mkasimd opened this issue 1 year ago • 3 comments

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"}

mkasimd avatar Apr 11 '24 14:04 mkasimd

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 ?

neolynx avatar Apr 17 '24 14:04 neolynx

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

mkasimd avatar Apr 18 '24 07:04 mkasimd

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.

neolynx avatar Apr 20 '24 21:04 neolynx

Thanks for the response. Adding "Batch":true was the solution apparently. It just worked

Could you also add the Batch option to the list describing the Signing Options in the Publish API documentation?

mkasimd avatar Jul 15 '24 12:07 mkasimd

Thanks for your inputs !

Documentation updated: https://www.aptly.info/doc/api/publish/ :-)

neolynx avatar Jul 15 '24 14:07 neolynx

@neolynx one small issue in the documentation: the type should be set to bool instead of string for completeness

also, thanks for your help with this!

mkasimd avatar Jul 16 '24 06:07 mkasimd