posit-dev / rsconnect-python

Command line interface for publishing to Posit Connect
https://docs.posit.co/rsconnect-python/
GNU General Public License v2.0
28 stars 20 forks source link

`rsconnect add server` with --cacert doesn't work with `.der` certs #413

Open kgartland-rstudio opened 1 year ago

kgartland-rstudio commented 1 year ago

Found while testing the --cacert fix, the .der cert never worked right with rsconnect add. We can run deploy and details with the .der certs but cannot rsconnect add.

We added .der support here: https://github.com/rstudio/rsconnect-python/pull/336

> rsconnect add -s https://alwayssecure.rsc:3443 -k {API_KEY} --cacert ~/Downloads/alwayssecure.rsc.der -n alwayssecureder
Detected the following inputs:
    name: COMMANDLINE
    server: COMMANDLINE
    api_key: COMMANDLINE
    insecure: DEFAULT
    cacert: COMMANDLINE
Checking https://alwayssecure.rsc:3443...        [OK]
Checking API key...                              [OK]
Traceback (most recent call last):
  File "/Users/kgartland/.pyenv/versions/rsconnect-tag/bin/rsconnect", line 8, in <module>
    sys.exit(cli())
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/core.py", line 1657, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/rsconnect/main.py", line 169, in wrapper
    return func(*args, **kwargs)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/click/decorators.py", line 26, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/rsconnect/main.py", line 463, in add
    server_store.set(
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/rsconnect/metadata.py", line 303, in set
    self._set(name, {**common_data, **target_data})
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/rsconnect/metadata.py", line 155, in _set
    self.save()
  File "/Users/kgartland/.pyenv/versions/3.8.2/envs/rsconnect-tag/lib/python3.8/site-packages/rsconnect/metadata.py", line 207, in save
    data = json.dumps(self._data, indent=4).encode("utf-8")
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/__init__.py", line 234, in dumps
    return cls(
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 431, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 405, in _iterencode_dict
    yield from chunks
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 438, in _iterencode
    o = _default(o)
  File "/Users/kgartland/.pyenv/versions/3.8.2/lib/python3.8/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type bytes is not JSON serializable
tdstein commented 9 months ago

This is tricky.

The underlying issue is that credentials are serialized into JSON format before being written to disk. The DER certificates are binary, which cannot be safely converted into a string. This is what triggers the error above.

To resolve this issue, we'll need to rewrite the storage layer to recognize and handle DER certificates. One option is to store the absolute file path instead of the file contents. At this point in the code, there is no reference to the file path, so the refactor will bubble up to main.py.

Another option would be to change the storage to something that can handle bytes, like Pickle.

mmarchetti commented 9 months ago

Can we convert to an encoding that's JSON-safe? (We would also need to store the encoding)

tdstein commented 9 months ago

Base64 might work. I'll run some experiments to verify.