unit icon indicating copy to clipboard operation
unit copied to clipboard

Need a better way to update certificate.

Open hongzhidao opened this issue 7 years ago • 13 comments

Assume that we have listen socket bind certificate.

curl -X PUT --data-binary @bundle.pem http://.../certificates/bundle
{
    "listeners": {
        "127.0.0.1:8080": {
            "application": "wsgi-app",
            "tls": {
                "certificate": "bundle"
            }
        }
    }
}

If we need to change the certificate content, we have to do by the following steps.

  1. Delete the association of listen and certificate.
  2. Delete certificate.
  3. Put certificate.
  4. Set the certificate to the listen again.

Is it right?

If that, we need a more convinient way to finish it only by one step.

curl -X PUT --data-binary @bundle.pem http://.../certificates/bundle

Thanks.

hongzhidao avatar Dec 07 '18 08:12 hongzhidao

Currently, you can do the following steps:

  1. Put a new certificate under the new name "bundle2";
  2. Switch listening socket to the new certificate (PUT "bundle2" /config/listeners/127.0.0.1:8080/tls/certificate);
  3. (Optionally) Delete the old one.

That was just easier to implement the way it works now. In the future we should make it more convenient, just in one step as you mentioned. It needs additional work.

VBart avatar Dec 07 '18 11:12 VBart

@VBart take a look, please.

  1. Support pretty json in configure. Since we often look over conf.json file, it would be better to support a pretty format.
  2. Auto sends the current conf to router process if the exists cert file is updated.
diff -r aae6699f4eee src/nxt_controller.c
--- a/src/nxt_controller.c	Tue Oct 29 16:07:21 2019 +0300
+++ b/src/nxt_controller.c	Mon Nov 04 22:01:42 2019 +0800
@@ -80,6 +80,10 @@
     nxt_controller_request_t *req, nxt_str_t *path);
 static void nxt_controller_process_cert_save(nxt_task_t *task,
     nxt_port_recv_msg_t *msg, void *data);
+static void nxt_controller_process_cert_update(nxt_task_t *task,
+    nxt_port_recv_msg_t *msg, void *data);
+static void nxt_controller_process_cert_handler(nxt_task_t *task,
+    nxt_port_recv_msg_t *msg, void *data);
 static nxt_bool_t nxt_controller_cert_in_use(nxt_str_t *name);
 #endif
 static void nxt_controller_conf_handler(nxt_task_t *task,
@@ -353,12 +357,13 @@
 nxt_controller_conf_send(nxt_task_t *task, nxt_conf_value_t *conf,
     nxt_port_rpc_handler_t handler, void *data)
 {
-    size_t         size;
-    uint32_t       stream;
-    nxt_int_t      rc;
-    nxt_buf_t      *b;
-    nxt_port_t     *router_port, *controller_port;
-    nxt_runtime_t  *rt;
+    size_t                  size;
+    uint32_t                stream;
+    nxt_int_t               rc;
+    nxt_buf_t               *b;
+    nxt_port_t              *router_port, *controller_port;
+    nxt_runtime_t           *rt;
+    nxt_conf_json_pretty_t  pretty;

     rt = task->thread->runtime;

@@ -370,14 +375,18 @@

     controller_port = rt->port_by_type[NXT_PROCESS_CONTROLLER];

-    size = nxt_conf_json_length(conf, NULL);
+    nxt_memzero(&pretty, sizeof(nxt_conf_json_pretty_t));
+
+    size = nxt_conf_json_length(conf, &pretty);

     b = nxt_port_mmap_get_buf(task, router_port, size);
     if (nxt_slow_path(b == NULL)) {
         return NXT_ERROR;
     }

-    b->mem.free = nxt_conf_json_print(b->mem.free, conf, NULL);
+    nxt_memzero(&pretty, sizeof(nxt_conf_json_pretty_t));
+
+    b->mem.free = nxt_conf_json_print(b->mem.free, conf, &pretty);

     stream = nxt_port_rpc_register_handler(task, controller_port,
                                            handler, handler,
@@ -1281,7 +1290,9 @@
     if (nxt_str_eq(&req->parser.method, "PUT", 3)) {
         value = nxt_cert_info_get(&name);
         if (value != NULL) {
-            goto exists_cert;
+            nxt_cert_store_get(task, &name, c->mem_pool,
+                               nxt_controller_process_cert_update, req);
+            return;
         }

         cert = nxt_cert_mem(task, &c->read->mem);
@@ -1346,15 +1357,6 @@
     nxt_controller_response(task, req, &resp);
     return;

-exists_cert:
-
-    resp.status = 400;
-    resp.title = (u_char *) "Certificate already exists.";
-    resp.offset = -1;
-
-    nxt_controller_response(task, req, &resp);
-    return;
-
 cert_in_use:

     resp.status = 400;
@@ -1394,6 +1396,72 @@


 static void
+nxt_controller_process_cert_update(nxt_task_t *task, nxt_port_recv_msg_t *msg,
+    void *data)
+{
+    nxt_int_t                 ret;
+    nxt_conn_t                *c;
+    nxt_buf_mem_t             *mbuf;
+    nxt_controller_request_t  *req;
+    nxt_controller_response_t  resp;
+
+    req = data;
+
+    if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) {
+        goto fail;
+    }
+
+    c = req->conn;
+
+    mbuf = &c->read->mem;
+
+    nxt_fd_write(msg->fd, mbuf->pos, nxt_buf_mem_used_size(mbuf));
+
+    nxt_fd_close(msg->fd);
+
+    ret = nxt_controller_conf_send(task, nxt_controller_conf.root,
+                                   nxt_controller_process_cert_handler, req);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        return;
+    }
+
+fail:
+
+    nxt_memzero(&resp, sizeof(nxt_controller_response_t));
+
+    resp.status = 500;
+    resp.title = (u_char *) "Failed to store certificate.";
+
+    nxt_controller_response(task, req, &resp);
+}
+
+
+static void
+nxt_controller_process_cert_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
+    void *data)
+{
+    nxt_controller_request_t   *req;
+    nxt_controller_response_t  resp;
+
+    req = data;
+
+    nxt_memzero(&resp, sizeof(nxt_controller_response_t));
+
+    if (msg->port_msg.type != NXT_PORT_MSG_RPC_READY) {
+        resp.status = 500;
+        resp.title = (u_char *) "Failed to store certificate.";
+
+    } else {
+        resp.status = 200;
+        resp.title = (u_char *) "Certificate chain uploaded.";
+    }
+
+    nxt_controller_response(task, req, &resp);
+}
+
+
+static void
 nxt_controller_process_cert_save(nxt_task_t *task, nxt_port_recv_msg_t *msg,
     void *data)
 {

hongzhidao avatar Nov 06 '19 08:11 hongzhidao

@hongzhidao

  1. The state directory is a blackbox. Nobody should look or touch anything inside it. All the changes must be done only using API (otherwise we will face with endless consistency problems). The content of the directory will be changed over time and may turn into binary some day. Making it readable is against the concept.
  2. One of major concepts of API configuration is that all the modifications must be atomic. From your patch it's not clear how do you revert to the previous certificate if anything goes wrong.

VBart avatar Nov 06 '19 12:11 VBart

For those coming here looking for a way to use certbot with nginx-unit, here's what everyone is talking about above in code.

function check_or_die()
{
  if [ "$1" != "200" ]; then
    kill "$(cat /opt/unit/unit.pid)"
    return 1
  fi
}
function update_certificate()
{

  bundle="/tmp/bundle-new"

  ( cat "$UNIT_PEM"; echo; cat "$UNIT_KEY_PEM" ) > "$bundle"

  NAME="bundle"

  RESP_CODE="$(curl \
      --output /tmp/bundle \
      --write-out '%{http_code}' \
      --silent \
      --request GET \
      --data-binary "@${bundle}" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME})"

  if [ "$RESP_CODE" == "404" ]; then
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request PUT \
      --data-binary "@${bundle}" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME}
  )"
  check_or_die "$RESP_CODE"
else

  if diff -q /tmp/bundle /tmp/bundle-new; then
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request PUT \
      --data-binary "@${bundle}" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME}-new
  )"
  check_or_die "$RESP_CODE"
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request PUT \
      --data "${NAME}-new" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/config/listeners/0.0.0.0:8443/tls/certificates
  )"
  check_or_die "$RESP_CODE"
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request DELETE \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME}
  )"
  check_or_die "$RESP_CODE"
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request PUT \
      --data-binary "@${bundle}" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME}
  )"
  check_or_die "$RESP_CODE"
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request PUT \
      --data "${NAME}" \
      --unix-socket $UNIT_SOCKET \
      http://localhost/config/listeners/0.0.0.0:8443/tls/certificates
  )"
  check_or_die "$RESP_CODE"
  RESP_CODE="$(
    curl \
      --output /dev/null \
      --write-out '%{http_code}' \
      --silent \
      --request DELETE \
      --unix-socket $UNIT_SOCKET \
      http://localhost/certificates/${NAME}-new
  )"
  check_or_die "$RESP_CODE"
  fi
fi
}

isodude avatar Oct 12 '21 05:10 isodude