Apache HTTPD
md_tailscale.c
Go to the documentation of this file.
1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18#include <stdlib.h>
19
20#include <apr_lib.h>
21#include <apr_strings.h>
22#include <apr_hash.h>
23#include <apr_uri.h>
24
25#include "md.h"
26#include "md_crypt.h"
27#include "md_json.h"
28#include "md_http.h"
29#include "md_log.h"
30#include "md_result.h"
31#include "md_reg.h"
32#include "md_store.h"
33#include "md_util.h"
34
35#include "md_tailscale.h"
36
45
47{
50 const char *ca_url;
52
54 ts_ctx = apr_pcalloc(d->p, sizeof(*ts_ctx));
55 ts_ctx->pool = d->p;
56 ts_ctx->driver = d;
57 ts_ctx->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));
58
59 ca_url = (d->md->ca_urls && !apr_is_empty_array(d->md->ca_urls))?
60 APR_ARRAY_IDX(d->md->ca_urls, 0, const char*) : NULL;
61 if (!ca_url) {
62 ca_url = MD_TAILSCALE_DEF_URL;
63 }
64 rv = apr_uri_parse(d->p, ca_url, &uri);
65 if (APR_SUCCESS != rv) {
66 md_result_printf(result, rv, "error parsing CA URL `%s`", ca_url);
67 goto leave;
68 }
69 if (uri.scheme && uri.scheme[0] && strcmp("file", uri.scheme)) {
70 rv = APR_ENOTIMPL;
71 md_result_printf(result, rv, "non `file` URLs not supported, CA URL is `%s`",
72 ca_url);
73 goto leave;
74 }
75 if (uri.hostname && uri.hostname[0] && strcmp("localhost", uri.hostname)) {
76 rv = APR_ENOTIMPL;
77 md_result_printf(result, rv, "non `localhost` URLs not supported, CA URL is `%s`",
78 ca_url);
79 goto leave;
80 }
81 ts_ctx->unix_socket_path = uri.path;
82 d->baton = ts_ctx;
83
84leave:
85 return rv;
86}
87
92
95{
96 apr_status_t rv;
97 md_t *md;
98 md_credentials_t *creds;
101 const char *name;
102 int i;
103
104 name = d->md->name;
105 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
106 /* Load data from MD_SG_STAGING and save it into "load_group".
107 */
108 if (APR_SUCCESS != (rv = md_load(d->store, MD_SG_STAGING, name, &md, d->p))) {
109 md_result_set(result, rv, "loading staged md.json");
110 goto leave;
111 }
112
113 /* tailscale generates one cert+key with key specification being whatever
114 * it chooses. Use the NULL spec here.
115 */
116 all_creds = apr_array_make(d->p, 5, sizeof(md_credentials_t*));
117 pkspec = NULL;
118 if (APR_SUCCESS != (rv = md_creds_load(d->store, MD_SG_STAGING, name, pkspec, &creds, d->p))) {
119 md_result_printf(result, rv, "loading staged credentials");
120 goto leave;
121 }
122 if (!creds->chain) {
123 rv = APR_ENOENT;
124 md_result_printf(result, rv, "no certificate in staged credentials");
125 goto leave;
126 }
127 if (APR_SUCCESS != (rv = md_check_cert_and_pkey(creds->chain, creds->pkey))) {
128 md_result_printf(result, rv, "certificate and private key do not match in staged credentials");
129 goto leave;
130 }
132
133 md_result_activity_setn(result, "purging store tmp space");
134 rv = md_store_purge(d->store, d->p, load_group, name);
135 if (APR_SUCCESS != rv) {
137 goto leave;
138 }
139
140 md_result_activity_setn(result, "saving staged md/privkey/pubcert");
141 if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
142 md_result_set(result, rv, "writing md.json");
143 goto leave;
144 }
145
146 for (i = 0; i < all_creds->nelts; ++i) {
148 if (APR_SUCCESS != (rv = md_creds_save(d->store, d->p, load_group, name, creds, 1))) {
149 md_result_printf(result, rv, "writing credentials #%d", i);
150 goto leave;
151 }
152 }
153
154 md_result_set(result, APR_SUCCESS, "saved staged data successfully");
155
156leave:
158 return rv;
159}
160
162{
163 switch (res->status) {
164 case 200:
165 return APR_SUCCESS;
166 case 400:
167 return APR_EINVAL;
168 case 401: /* sectigo returns this instead of 403 */
169 case 403:
170 return APR_EACCES;
171 case 404:
172 return APR_ENOENT;
173 default:
174 return APR_EGENERAL;
175 }
176 return APR_SUCCESS;
177}
178
180{
182 apr_status_t rv;
183
184 rv = rv_of_response(res);
185 if (APR_SUCCESS != rv) goto leave;
186 apr_array_clear(ts_ctx->chain);
187 rv = md_cert_chain_read_http(ts_ctx->chain, ts_ctx->pool, res);
188 if (APR_SUCCESS != rv) goto leave;
189
190leave:
191 return rv;
192}
193
195{
197 apr_status_t rv;
198
199 rv = rv_of_response(res);
200 if (APR_SUCCESS != rv) goto leave;
201 rv = md_pkey_read_http(&ts_ctx->pkey, ts_ctx->pool, res);
202 if (APR_SUCCESS != rv) goto leave;
203
204leave:
205 return rv;
206}
207
209{
210 const char *name, *domain, *url;
212 ts_ctx_t *ts_ctx = d->baton;
213 md_http_t *http;
214 const md_pubcert_t *pubcert;
215 md_cert_t *old_cert, *new_cert;
216 int reset_staging = d->reset;
217
218 /* "renewing" the certificate from tailscale. Since tailscale has its
219 * own ideas on when to do this, we can only inspect the certificate
220 * it gives us and see if it is different from the current one we have.
221 * (if we have any. first time, lacking a cert, any it gives us is
222 * considered as 'renewed'.)
223 */
224 name = d->md->name;
225 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: renewing cert", name);
226
227 /* When not explicitly told to reset, we check the existing data. If
228 * it is incomplete or old, we trigger the reset for a clean start. */
229 if (!reset_staging) {
230 md_result_activity_setn(result, "Checking staging area");
231 rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ts_ctx->md, d->p);
232 if (APR_SUCCESS == rv) {
233 /* So, we have a copy in staging, but is it a recent or an old one? */
234 if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) {
235 reset_staging = 1;
236 }
237 }
238 else if (APR_STATUS_IS_ENOENT(rv)) {
239 reset_staging = 1;
240 rv = APR_SUCCESS;
241 }
242 }
243
244 if (reset_staging) {
245 md_result_activity_setn(result, "Resetting staging area");
246 /* reset the staging area for this domain */
247 rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
249 "%s: reset staging area", d->md->name);
250 if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
251 md_result_printf(result, rv, "resetting staging area");
252 goto leave;
253 }
254 rv = APR_SUCCESS;
255 ts_ctx->md = NULL;
256 }
257
258 if (!ts_ctx->md || !md_array_str_eq(ts_ctx->md->ca_urls, d->md->ca_urls, 1)) {
259 md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
260 /* re-initialize staging */
261 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: setup staging", d->md->name);
262 md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
263 ts_ctx->md = md_copy(d->p, d->md);
264 rv = md_save(d->store, d->p, MD_SG_STAGING, ts_ctx->md, 0);
265 if (APR_SUCCESS != rv) {
266 md_result_printf(result, rv, "Saving MD information in staging area.");
268 goto leave;
269 }
270 }
271
272 if (!ts_ctx->unix_socket_path) {
273 rv = APR_ENOTIMPL;
274 md_result_set(result, rv, "only unix sockets are supported for tailscale connections");
275 goto leave;
276 }
277
278 rv = md_util_is_unix_socket(ts_ctx->unix_socket_path, d->p);
279 if (APR_SUCCESS != rv) {
280 md_result_printf(result, rv, "tailscale socket not available, may not be up: %s",
281 ts_ctx->unix_socket_path);
282 goto leave;
283 }
284
285 rv = md_http_create(&http, d->p,
286 apr_psprintf(d->p, "Apache mod_md/%s", MOD_MD_VERSION),
287 NULL);
288 if (APR_SUCCESS != rv) {
289 md_result_set(result, rv, "creating http context");
290 goto leave;
291 }
293
294 domain = (d->md->domains->nelts > 0)?
295 APR_ARRAY_IDX(d->md->domains, 0, const char*) : NULL;
296 if (!domain) {
297 rv = APR_EINVAL;
298 md_result_set(result, rv, "no domain names available");
299 }
300
301 url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=crt",
302 domain);
304 if (APR_SUCCESS != rv) {
305 md_result_set(result, rv, "retrieving certificate from tailscale");
306 goto leave;
307 }
308 if (ts_ctx->chain->nelts <= 0) {
309 rv = APR_ENOENT;
310 md_result_set(result, rv, "tailscale returned no certificates");
311 goto leave;
312 }
313
314 /* Got the key and the chain, is it new? */
315 rv = md_reg_get_pubcert(&pubcert, d->reg,d->md, 0, d->p);
316 if (APR_SUCCESS == rv) {
318 new_cert = APR_ARRAY_IDX(ts_ctx->chain, 0, md_cert_t*);
319 if (md_certs_are_equal(old_cert, new_cert)) {
320 /* tailscale has not renewed the certificate, yet */
321 rv = APR_ENOENT;
322 md_result_set(result, rv, "tailscale has not renewed the certificate yet");
323 /* let's check this daily */
325 goto leave;
326 }
327 }
328
329 /* We have a new certificate (or had none before).
330 * Get the key and store both in STAGING.
331 */
332 url = apr_psprintf(d->p, "http://localhost/localapi/v0/cert/%s?type=key",
333 domain);
335 if (APR_SUCCESS != rv) {
336 md_result_set(result, rv, "retrieving key from tailscale");
337 goto leave;
338 }
339
340 rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, name, NULL, ts_ctx->pkey, 1);
341 if (APR_SUCCESS != rv) {
342 md_result_set(result, rv, "saving private key");
343 goto leave;
344 }
345
346 rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, name,
347 NULL, ts_ctx->chain, 1);
348 if (APR_SUCCESS != rv) {
349 md_result_printf(result, rv, "saving new certificate chain.");
350 goto leave;
351 }
352
354 "A new tailscale certificate has been retrieved successfully and can "
355 "be used. A graceful server restart is recommended.");
356
357leave:
359 return rv;
360}
361
363{
364 (void)p;
365 if (!md->ca_urls) {
366 md->ca_urls = apr_array_make(p, 3, sizeof(const char *));
367 APR_ARRAY_PUSH(md->ca_urls, const char*) = MD_TAILSCALE_DEF_URL;
368 }
369 return APR_SUCCESS;
370}
371
372
377
APR Hash Tables.
APR general purpose library routines.
apr_size_t const unsigned char unsigned int unsigned int d
Definition apr_siphash.h:72
APR Strings library.
APR-UTIL URI Routines.
ap_vhost_iterate_conn_cb void * baton
Definition http_vhost.h:87
#define APR_EGENERAL
Definition apr_errno.h:313
#define APR_EACCES
Definition apr_errno.h:641
#define APR_ENOTIMPL
Definition apr_errno.h:476
#define APR_ENOENT
Definition apr_errno.h:662
#define APR_EINVAL
Definition apr_errno.h:711
#define APR_STATUS_IS_ENOENT(s)
Definition apr_errno.h:1246
apr_pool_t apr_dbd_t apr_dbd_results_t ** res
Definition apr_dbd.h:287
const char * url
Definition apr_escape.h:120
const char * uri
Definition apr_uri.h:159
apr_size_t size
#define APR_SUCCESS
Definition apr_errno.h:225
int apr_status_t
Definition apr_errno.h:44
apr_array_header_t ** result
#define apr_pcalloc(p, size)
Definition apr_pools.h:465
#define APR_ARRAY_PUSH(ary, type)
Definition apr_tables.h:150
#define APR_ARRAY_IDX(ary, i, type)
Definition apr_tables.h:141
#define apr_time_from_sec(sec)
Definition apr_time.h:78
md_t * md_copy(apr_pool_t *p, const md_t *src)
Definition md_core.c:210
apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain, apr_pool_t *p, const struct md_http_response_t *res)
Definition md_crypt.c:1540
int md_certs_are_equal(const md_cert_t *a, const md_cert_t *b)
Definition md_crypt.c:1196
apr_status_t md_pkey_read_http(md_pkey_t **ppkey, apr_pool_t *pool, const struct md_http_response_t *res)
Definition md_crypt.c:706
apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t *pkey)
Definition md_crypt.c:2131
apr_pool_t * p
Definition md_event.c:32
apr_status_t md_http_GET_perform(struct md_http_t *http, const char *url, struct apr_table_t *headers, md_http_response_cb *cb, void *baton)
Definition md_http.c:349
void md_http_set_unix_socket_path(md_http_t *http, const char *path)
Definition md_http.c:166
apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent, const char *proxy_url)
Definition md_http.c:61
void md_log_perror(const char *file, int line, md_log_level_t level, apr_status_t rv, apr_pool_t *p, const char *fmt,...)
Definition md_log.c:68
#define MD_LOG_MARK
Definition md_log.h:39
@ MD_LOG_TRACE1
Definition md_log.h:29
@ MD_LOG_ERR
Definition md_log.h:24
@ MD_LOG_DEBUG
Definition md_log.h:28
apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, const md_t *md, int i, apr_pool_t *p)
Definition md_reg.c:608
void md_result_activity_printf(md_result_t *result, const char *fmt,...)
Definition md_result.c:82
void md_result_activity_setn(md_result_t *result, const char *activity)
Definition md_result.c:74
void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt,...)
Definition md_result.c:126
void md_result_log(md_result_t *result, unsigned int level)
Definition md_result.c:227
void md_result_set(md_result_t *result, apr_status_t status, const char *detail)
Definition md_result.c:91
void md_result_delay_set(md_result_t *result, apr_time_t ready_at)
Definition md_result.c:138
apr_status_t md_creds_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, md_credentials_t *creds, int create)
Definition md_store.c:329
apr_status_t md_creds_load(md_store_t *store, md_store_group_t group, const char *name, md_pkey_spec_t *spec, md_credentials_t **pcreds, apr_pool_t *p)
Definition md_store.c:311
apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, md_pkey_spec_t *spec, struct apr_array_header_t *pubcert, int create)
Definition md_store.c:303
int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2, const char *name, apr_pool_t *p)
Definition md_store.c:245
apr_status_t md_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, md_t *md, int create)
Definition md_store.c:211
apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name)
Definition md_store.c:93
apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, const char *name, md_pkey_spec_t *spec, struct md_pkey_t *pkey, int create)
Definition md_store.c:288
apr_status_t md_load(md_store_t *store, md_store_group_t group, const char *name, md_t **pmd, apr_pool_t *p)
Definition md_store.c:179
md_store_group_t
Definition md_store.h:62
@ MD_SG_STAGING
Definition md_store.h:67
@ MD_SG_DOMAINS
Definition md_store.h:66
static apr_status_t rv_of_response(const md_http_response_t *res)
static md_proto_t TAILSCALE_PROTO
static apr_status_t ts_complete_md(md_t *md, apr_pool_t *p)
static apr_status_t on_get_cert(const md_http_response_t *res, void *baton)
static apr_status_t ts_preload(md_proto_driver_t *d, md_store_group_t load_group, md_result_t *result)
apr_status_t md_tailscale_protos_add(apr_hash_t *protos, apr_pool_t *p)
static apr_status_t ts_renew(md_proto_driver_t *d, md_result_t *result)
static apr_status_t ts_init(md_proto_driver_t *d, md_result_t *result)
static apr_status_t ts_preload_init(md_proto_driver_t *d, md_result_t *result)
static apr_status_t on_get_key(const md_http_response_t *res, void *baton)
#define MD_PROTO_TAILSCALE
#define MD_SECS_PER_DAY
Definition md_time.h:23
int md_array_str_eq(const struct apr_array_header_t *a1, const struct apr_array_header_t *a2, int case_sensitive)
Definition md_util.c:268
apr_status_t md_util_is_unix_socket(const char *path, apr_pool_t *pool)
Definition md_util.c:401
#define MOD_MD_VERSION
Definition md_version.h:30
#define MD_TAILSCALE_DEF_URL
Definition md_version.h:41
return NULL
Definition mod_so.c:359
int i
Definition mod_so.c:347
char * name
struct apr_array_header_t * chain
Definition md_store.h:269
struct md_pkey_t * pkey
Definition md_store.h:268
const char * unix_socket_path
Definition md_http.c:36
Definition md.h:76
struct apr_array_header_t * ca_urls
Definition md.h:86
apr_pool_t * pool
const char * unix_socket_path
apr_array_header_t * chain
md_pkey_t * pkey
md_proto_driver_t * driver
md_t * md