mirror of
https://github.com/systemd/systemd.git
synced 2026-08-09 01:20:53 +00:00
resolvectl: support JSON output for openpgp and tlsa
The openpgp and tlsa verbs rejected --json= even though they already determine the RR type they query. Allow JSON output for these shortcut commands. Accept matching explicit --type= values as well, but reject mismatched types so openpgp only queries OPENPGPKEY records and tlsa only queries TLSA records. Refuse unauthenticated key records in JSON format so scripts do not accidentally consume cryptographic DNS data that would only produce a warning in human-readable output. Fixes #43245
This commit is contained in:
committed by
Yu Watanabe
parent
1652be7df8
commit
05ea7e87b6
@@ -103,6 +103,12 @@
|
||||
are converted to the corresponding DNS domain name, and any <constant class='dns'>OPENPGPKEY</constant>
|
||||
keys are printed.</para>
|
||||
|
||||
<para>The resource record type is implied by this command. If <option>--type=</option> is specified,
|
||||
it must be <constant class='dns'>OPENPGPKEY</constant>.</para>
|
||||
|
||||
<para>If combined with <option>--json=</option>, outputs the resource record data in JSON format.
|
||||
Unauthenticated key data is refused in JSON format.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v239"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -119,6 +125,12 @@
|
||||
<constant>443</constant> will be used by default. The family may be specified as the first argument,
|
||||
otherwise <constant>tcp</constant> will be used.</para>
|
||||
|
||||
<para>The resource record type is implied by this command. If <option>--type=</option> is specified,
|
||||
it must be <constant class='dns'>TLSA</constant>.</para>
|
||||
|
||||
<para>If combined with <option>--json=</option>, outputs the resource record data in JSON format.
|
||||
Unauthenticated key data is refused in JSON format.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v239"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
||||
@@ -567,6 +567,8 @@ static int output_rr_packet(DnsResourceRecord *rr, int ifindex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DEFINE_POINTER_ARRAY_FREE_FUNC(DnsResourceRecord*, dns_resource_record_unref);
|
||||
|
||||
static int idna_candidate(const char *name, char **ret) {
|
||||
_cleanup_free_ char *idnafied = NULL;
|
||||
int r;
|
||||
@@ -668,32 +670,50 @@ static int resolve_record(const char *name, uint16_t class, uint16_t type, bool
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (reply.n_records == 0) {
|
||||
if (warn_missing)
|
||||
log_error("%s: no records found", name);
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
DnsResourceRecord **rrs = new0(DnsResourceRecord*, reply.n_records);
|
||||
size_t n_rrs = reply.n_records;
|
||||
if (!rrs)
|
||||
return log_oom();
|
||||
CLEANUP_ARRAY(rrs, n_rrs, dns_resource_record_unref_array);
|
||||
|
||||
bool json = sd_json_format_enabled(arg_json_format_flags);
|
||||
bool needs_authentication = false;
|
||||
FOREACH_ARRAY(record, reply.records, reply.n_records) {
|
||||
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
|
||||
r = dns_resource_record_new_from_raw(&rr, record->raw.iov_base, record->raw.iov_len);
|
||||
size_t i = record - reply.records;
|
||||
|
||||
r = dns_resource_record_new_from_raw(&rrs[i], record->raw.iov_base, record->raw.iov_len);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse RR: %m");
|
||||
|
||||
if (dns_type_needs_authentication(rrs[i]->key->type)) {
|
||||
needs_authentication = true;
|
||||
|
||||
if (json && !FLAGS_SET(reply.flags, SD_RESOLVED_AUTHENTICATED))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED),
|
||||
"Refusing to output unauthenticated DNS records that require "
|
||||
"authentication in JSON format.");
|
||||
}
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(record, reply.records, reply.n_records) {
|
||||
size_t i = record - reply.records;
|
||||
|
||||
if (arg_raw == RAW_PACKET) {
|
||||
uint64_t u64 = htole64(record->raw.iov_len);
|
||||
|
||||
fwrite(&u64, sizeof(u64), 1, stdout);
|
||||
fwrite(record->raw.iov_base, 1, record->raw.iov_len, stdout);
|
||||
} else {
|
||||
r = output_rr_packet(rr, record->ifindex);
|
||||
r = output_rr_packet(rrs[i], record->ifindex);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (dns_type_needs_authentication(rr->key->type))
|
||||
needs_authentication = true;
|
||||
}
|
||||
|
||||
if (reply.n_records == 0) {
|
||||
if (warn_missing)
|
||||
log_error("%s: no records found", name);
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
print_source(reply.flags, ts);
|
||||
@@ -1042,8 +1062,8 @@ static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata)
|
||||
#if HAVE_OPENSSL
|
||||
int ret = 0;
|
||||
|
||||
if (sd_json_format_enabled(arg_json_format_flags))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
|
||||
if (!IN_SET(arg_type, 0, DNS_TYPE_OPENPGPKEY))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The openpgp command may only be combined with --type=OPENPGPKEY.");
|
||||
|
||||
STRV_FOREACH(p, strv_skip(argv, 1))
|
||||
RET_GATHER(ret, resolve_openpgp(*p));
|
||||
@@ -1098,8 +1118,8 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
|
||||
assert(argc >= 2);
|
||||
|
||||
if (sd_json_format_enabled(arg_json_format_flags))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format.");
|
||||
if (!IN_SET(arg_type, 0, DNS_TYPE_TLSA))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The tlsa command may only be combined with --type=TLSA.");
|
||||
|
||||
if (service_family_is_valid(argv[1])) {
|
||||
family = argv[1];
|
||||
@@ -3659,6 +3679,9 @@ static int native_parse_argv(int argc, char *argv[], char ***remaining_args) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (arg_raw != RAW_NONE && sd_json_format_enabled(arg_json_format_flags))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--raw and --json= may not be combined.");
|
||||
|
||||
if (arg_type == 0 && arg_class != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--class= may only be used in conjunction with --type=.");
|
||||
|
||||
@@ -2264,6 +2264,22 @@ TEST(dns_resource_record_to_json_soa) {
|
||||
ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(j, "minimum")), UINT64_C(3600));
|
||||
}
|
||||
|
||||
TEST(dns_resource_record_to_json_openpgpkey) {
|
||||
static const uint8_t data[] = { 0, 1, 2, 3 };
|
||||
|
||||
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr =
|
||||
ASSERT_NOT_NULL(dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_OPENPGPKEY, "www.example.com"));
|
||||
ASSERT_NOT_NULL(rr->generic.data = memdup(data, sizeof(data)));
|
||||
rr->generic.data_size = sizeof(data);
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
|
||||
ASSERT_OK(dns_resource_record_to_json(rr, &j));
|
||||
|
||||
sd_json_variant *key = ASSERT_NOT_NULL(sd_json_variant_by_key(j, "key"));
|
||||
ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(key, "type")), (uint64_t) DNS_TYPE_OPENPGPKEY);
|
||||
ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(j, "data")), "AAECAw==");
|
||||
}
|
||||
|
||||
TEST(dns_resource_record_to_string_ptr) {
|
||||
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
|
||||
const char *str;
|
||||
|
||||
@@ -2487,6 +2487,12 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) {
|
||||
SD_JSON_BUILD_PAIR_UNSIGNED("matchingType", rr->tlsa.matching_type),
|
||||
SD_JSON_BUILD_PAIR_HEX("data", rr->tlsa.data, rr->tlsa.data_size));
|
||||
|
||||
case DNS_TYPE_OPENPGPKEY:
|
||||
return sd_json_buildo(
|
||||
ret,
|
||||
SD_JSON_BUILD_PAIR_VARIANT("key", k),
|
||||
SD_JSON_BUILD_PAIR_BASE64("data", rr->generic.data, rr->generic.data_size));
|
||||
|
||||
case DNS_TYPE_SVCB:
|
||||
case DNS_TYPE_HTTPS: {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *p = NULL;
|
||||
|
||||
@@ -24,3 +24,5 @@ mail A 10.0.0.122
|
||||
myservice A 10.0.0.123
|
||||
AAAA fd00:dead:beef:cafe::123
|
||||
_mysvc._tcp SRV 10 5 1234 myservice
|
||||
|
||||
5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey OPENPGPKEY AAECAw==
|
||||
|
||||
@@ -607,6 +607,20 @@ testcase_08_resolved() {
|
||||
run resolvectl openpgp mr.smith@signed.test
|
||||
grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT"
|
||||
grep -qF "authenticated: yes" "$RUN_OUT"
|
||||
run resolvectl openpgp mr.smith@signed.test --json=short
|
||||
grep -qF '"key":{"class":1,"type":61,"name":"5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test"}' "$RUN_OUT"
|
||||
grep -qF '"data":"' "$RUN_OUT"
|
||||
(! run resolvectl openpgp mr.smith@untrusted.test --json=short)
|
||||
grep -qF "Refusing to output unauthenticated DNS records that require authentication in JSON format." "$RUN_OUT"
|
||||
run resolvectl openpgp mr.smith@signed.test --json=short --type=OPENPGPKEY
|
||||
grep -qF '"key":{"class":1,"type":61,"name":"5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test"}' "$RUN_OUT"
|
||||
grep -qF '"data":"' "$RUN_OUT"
|
||||
(! run resolvectl openpgp mr.smith@signed.test --json=short --type=A)
|
||||
grep -qF -- "The openpgp command may only be combined with --type=OPENPGPKEY." "$RUN_OUT"
|
||||
(! run resolvectl tlsa signed.test:invalid --json=short)
|
||||
grep -qF 'Invalid port "invalid".' "$RUN_OUT"
|
||||
(! run resolvectl tlsa signed.test --json=short --type=A)
|
||||
grep -qF -- "The tlsa command may only be combined with --type=TLSA." "$RUN_OUT"
|
||||
# Check zone transfers (AXFR/IXFR)
|
||||
# Note: since resolved doesn't support zone transfers, let's just make sure it
|
||||
# simply refuses such requests without choking on them
|
||||
|
||||
Reference in New Issue
Block a user