A human-friendly DSL for ATProto Lexicons
27
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add mlf-dns-route53 official plugin

Second official DNS plugin, proving the subprocess protocol is
provider-agnostic. Wraps aws-sdk-route53 for the four ops — zone
lookup by DNS name (walks parent domains and hits
list_hosted_zones_by_name), list_txt, upsert_txt, delete_txt. Route 53
lacks per-record IDs so (name, type) is the identity; upserts use a
single UPSERT ChangeResourceRecordSets. Credential schema: access_key
+ secret_key required, session_token (for STS) and region
(default us-east-1) optional.

TXT values are quoted + escape-sequence'd on the wire; a hand-rolled
unquote handles the trailing \" edge case that a naive
trim_matches would corrupt.

authored by stavola.xyz and committed by

Tangled d3168e46 c4fbd334

+1346 -30
+738 -30
Cargo.lock
··· 185 185 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 186 186 187 187 [[package]] 188 + name = "aws-config" 189 + version = "1.8.14" 190 + source = "registry+https://github.com/rust-lang/crates.io-index" 191 + checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2" 192 + dependencies = [ 193 + "aws-credential-types", 194 + "aws-runtime", 195 + "aws-sdk-sso", 196 + "aws-sdk-ssooidc", 197 + "aws-sdk-sts", 198 + "aws-smithy-async", 199 + "aws-smithy-http", 200 + "aws-smithy-json", 201 + "aws-smithy-runtime", 202 + "aws-smithy-runtime-api", 203 + "aws-smithy-types", 204 + "aws-types", 205 + "bytes", 206 + "fastrand", 207 + "hex", 208 + "http 1.3.1", 209 + "ring", 210 + "time", 211 + "tokio", 212 + "tracing", 213 + "url", 214 + "zeroize", 215 + ] 216 + 217 + [[package]] 218 + name = "aws-credential-types" 219 + version = "1.2.12" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "e26bbf46abc608f2dc61fd6cb3b7b0665497cc259a21520151ed98f8b37d2c79" 222 + dependencies = [ 223 + "aws-smithy-async", 224 + "aws-smithy-runtime-api", 225 + "aws-smithy-types", 226 + "zeroize", 227 + ] 228 + 229 + [[package]] 230 + name = "aws-lc-rs" 231 + version = "1.16.3" 232 + source = "registry+https://github.com/rust-lang/crates.io-index" 233 + checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" 234 + dependencies = [ 235 + "aws-lc-sys", 236 + "zeroize", 237 + ] 238 + 239 + [[package]] 240 + name = "aws-lc-sys" 241 + version = "0.40.0" 242 + source = "registry+https://github.com/rust-lang/crates.io-index" 243 + checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" 244 + dependencies = [ 245 + "cc", 246 + "cmake", 247 + "dunce", 248 + "fs_extra", 249 + ] 250 + 251 + [[package]] 252 + name = "aws-runtime" 253 + version = "1.7.0" 254 + source = "registry+https://github.com/rust-lang/crates.io-index" 255 + checksum = "b0f92058d22a46adf53ec57a6a96f34447daf02bff52e8fb956c66bcd5c6ac12" 256 + dependencies = [ 257 + "aws-credential-types", 258 + "aws-sigv4", 259 + "aws-smithy-async", 260 + "aws-smithy-http", 261 + "aws-smithy-runtime", 262 + "aws-smithy-runtime-api", 263 + "aws-smithy-types", 264 + "aws-types", 265 + "bytes", 266 + "bytes-utils", 267 + "fastrand", 268 + "http 1.3.1", 269 + "http-body 1.0.1", 270 + "percent-encoding", 271 + "pin-project-lite", 272 + "tracing", 273 + "uuid", 274 + ] 275 + 276 + [[package]] 277 + name = "aws-sdk-route53" 278 + version = "1.107.0" 279 + source = "registry+https://github.com/rust-lang/crates.io-index" 280 + checksum = "9edbdedafe825712933d180cfe6b1cedeb37543371a4b6da8f1c0f48d40ac82e" 281 + dependencies = [ 282 + "aws-credential-types", 283 + "aws-runtime", 284 + "aws-smithy-async", 285 + "aws-smithy-http", 286 + "aws-smithy-json", 287 + "aws-smithy-observability", 288 + "aws-smithy-runtime", 289 + "aws-smithy-runtime-api", 290 + "aws-smithy-types", 291 + "aws-smithy-xml", 292 + "aws-types", 293 + "fastrand", 294 + "http 0.2.12", 295 + "http 1.3.1", 296 + "regex-lite", 297 + "tracing", 298 + ] 299 + 300 + [[package]] 301 + name = "aws-sdk-sso" 302 + version = "1.94.0" 303 + source = "registry+https://github.com/rust-lang/crates.io-index" 304 + checksum = "699da1961a289b23842d88fe2984c6ff68735fdf9bdcbc69ceaeb2491c9bf434" 305 + dependencies = [ 306 + "aws-credential-types", 307 + "aws-runtime", 308 + "aws-smithy-async", 309 + "aws-smithy-http", 310 + "aws-smithy-json", 311 + "aws-smithy-observability", 312 + "aws-smithy-runtime", 313 + "aws-smithy-runtime-api", 314 + "aws-smithy-types", 315 + "aws-types", 316 + "bytes", 317 + "fastrand", 318 + "http 0.2.12", 319 + "http 1.3.1", 320 + "regex-lite", 321 + "tracing", 322 + ] 323 + 324 + [[package]] 325 + name = "aws-sdk-ssooidc" 326 + version = "1.96.0" 327 + source = "registry+https://github.com/rust-lang/crates.io-index" 328 + checksum = "e3e3a4cb3b124833eafea9afd1a6cc5f8ddf3efefffc6651ef76a03cbc6b4981" 329 + dependencies = [ 330 + "aws-credential-types", 331 + "aws-runtime", 332 + "aws-smithy-async", 333 + "aws-smithy-http", 334 + "aws-smithy-json", 335 + "aws-smithy-observability", 336 + "aws-smithy-runtime", 337 + "aws-smithy-runtime-api", 338 + "aws-smithy-types", 339 + "aws-types", 340 + "bytes", 341 + "fastrand", 342 + "http 0.2.12", 343 + "http 1.3.1", 344 + "regex-lite", 345 + "tracing", 346 + ] 347 + 348 + [[package]] 349 + name = "aws-sdk-sts" 350 + version = "1.98.0" 351 + source = "registry+https://github.com/rust-lang/crates.io-index" 352 + checksum = "89c4f19655ab0856375e169865c91264de965bd74c407c7f1e403184b1049409" 353 + dependencies = [ 354 + "aws-credential-types", 355 + "aws-runtime", 356 + "aws-smithy-async", 357 + "aws-smithy-http", 358 + "aws-smithy-json", 359 + "aws-smithy-observability", 360 + "aws-smithy-query", 361 + "aws-smithy-runtime", 362 + "aws-smithy-runtime-api", 363 + "aws-smithy-types", 364 + "aws-smithy-xml", 365 + "aws-types", 366 + "fastrand", 367 + "http 0.2.12", 368 + "http 1.3.1", 369 + "regex-lite", 370 + "tracing", 371 + ] 372 + 373 + [[package]] 374 + name = "aws-sigv4" 375 + version = "1.4.0" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "68f6ae9b71597dc5fd115d52849d7a5556ad9265885ad3492ea8d73b93bbc46e" 378 + dependencies = [ 379 + "aws-credential-types", 380 + "aws-smithy-http", 381 + "aws-smithy-runtime-api", 382 + "aws-smithy-types", 383 + "bytes", 384 + "form_urlencoded", 385 + "hex", 386 + "hmac", 387 + "http 0.2.12", 388 + "http 1.3.1", 389 + "percent-encoding", 390 + "sha2 0.10.9", 391 + "time", 392 + "tracing", 393 + ] 394 + 395 + [[package]] 396 + name = "aws-smithy-async" 397 + version = "1.2.12" 398 + source = "registry+https://github.com/rust-lang/crates.io-index" 399 + checksum = "3cba48474f1d6807384d06fec085b909f5807e16653c5af5c45dfe89539f0b70" 400 + dependencies = [ 401 + "futures-util", 402 + "pin-project-lite", 403 + "tokio", 404 + ] 405 + 406 + [[package]] 407 + name = "aws-smithy-http" 408 + version = "0.63.4" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "af4a8a5fe3e4ac7ee871237c340bbce13e982d37543b65700f4419e039f5d78e" 411 + dependencies = [ 412 + "aws-smithy-runtime-api", 413 + "aws-smithy-types", 414 + "bytes", 415 + "bytes-utils", 416 + "futures-core", 417 + "futures-util", 418 + "http 1.3.1", 419 + "http-body 1.0.1", 420 + "http-body-util", 421 + "percent-encoding", 422 + "pin-project-lite", 423 + "pin-utils", 424 + "tracing", 425 + ] 426 + 427 + [[package]] 428 + name = "aws-smithy-http-client" 429 + version = "1.1.10" 430 + source = "registry+https://github.com/rust-lang/crates.io-index" 431 + checksum = "0709f0083aa19b704132684bc26d3c868e06bd428ccc4373b0b55c3e8748a58b" 432 + dependencies = [ 433 + "aws-smithy-async", 434 + "aws-smithy-runtime-api", 435 + "aws-smithy-types", 436 + "h2 0.3.27", 437 + "h2 0.4.12", 438 + "http 0.2.12", 439 + "http 1.3.1", 440 + "http-body 0.4.6", 441 + "hyper 0.14.32", 442 + "hyper 1.7.0", 443 + "hyper-rustls 0.24.2", 444 + "hyper-rustls 0.27.7", 445 + "hyper-util", 446 + "pin-project-lite", 447 + "rustls 0.21.12", 448 + "rustls 0.23.32", 449 + "rustls-native-certs", 450 + "rustls-pki-types", 451 + "tokio", 452 + "tokio-rustls 0.26.4", 453 + "tower 0.5.2", 454 + "tracing", 455 + ] 456 + 457 + [[package]] 458 + name = "aws-smithy-json" 459 + version = "0.62.4" 460 + source = "registry+https://github.com/rust-lang/crates.io-index" 461 + checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb" 462 + dependencies = [ 463 + "aws-smithy-types", 464 + ] 465 + 466 + [[package]] 467 + name = "aws-smithy-observability" 468 + version = "0.2.5" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b" 471 + dependencies = [ 472 + "aws-smithy-runtime-api", 473 + ] 474 + 475 + [[package]] 476 + name = "aws-smithy-query" 477 + version = "0.60.14" 478 + source = "registry+https://github.com/rust-lang/crates.io-index" 479 + checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0" 480 + dependencies = [ 481 + "aws-smithy-types", 482 + "urlencoding", 483 + ] 484 + 485 + [[package]] 486 + name = "aws-smithy-runtime" 487 + version = "1.10.1" 488 + source = "registry+https://github.com/rust-lang/crates.io-index" 489 + checksum = "8fd3dfc18c1ce097cf81fced7192731e63809829c6cbf933c1ec47452d08e1aa" 490 + dependencies = [ 491 + "aws-smithy-async", 492 + "aws-smithy-http", 493 + "aws-smithy-http-client", 494 + "aws-smithy-observability", 495 + "aws-smithy-runtime-api", 496 + "aws-smithy-types", 497 + "bytes", 498 + "fastrand", 499 + "http 0.2.12", 500 + "http 1.3.1", 501 + "http-body 0.4.6", 502 + "http-body 1.0.1", 503 + "http-body-util", 504 + "pin-project-lite", 505 + "pin-utils", 506 + "tokio", 507 + "tracing", 508 + ] 509 + 510 + [[package]] 511 + name = "aws-smithy-runtime-api" 512 + version = "1.11.4" 513 + source = "registry+https://github.com/rust-lang/crates.io-index" 514 + checksum = "8c55e0837e9b8526f49e0b9bfa9ee18ddee70e853f5bc09c5d11ebceddcb0fec" 515 + dependencies = [ 516 + "aws-smithy-async", 517 + "aws-smithy-types", 518 + "bytes", 519 + "http 0.2.12", 520 + "http 1.3.1", 521 + "pin-project-lite", 522 + "tokio", 523 + "tracing", 524 + "zeroize", 525 + ] 526 + 527 + [[package]] 528 + name = "aws-smithy-types" 529 + version = "1.4.4" 530 + source = "registry+https://github.com/rust-lang/crates.io-index" 531 + checksum = "576b0d6991c9c32bc14fc340582ef148311f924d41815f641a308b5d11e8e7cd" 532 + dependencies = [ 533 + "base64-simd", 534 + "bytes", 535 + "bytes-utils", 536 + "futures-core", 537 + "http 0.2.12", 538 + "http 1.3.1", 539 + "http-body 0.4.6", 540 + "http-body 1.0.1", 541 + "http-body-util", 542 + "itoa", 543 + "num-integer", 544 + "pin-project-lite", 545 + "pin-utils", 546 + "ryu", 547 + "serde", 548 + "time", 549 + "tokio", 550 + "tokio-util", 551 + ] 552 + 553 + [[package]] 554 + name = "aws-smithy-xml" 555 + version = "0.60.15" 556 + source = "registry+https://github.com/rust-lang/crates.io-index" 557 + checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" 558 + dependencies = [ 559 + "xmlparser", 560 + ] 561 + 562 + [[package]] 563 + name = "aws-types" 564 + version = "1.3.12" 565 + source = "registry+https://github.com/rust-lang/crates.io-index" 566 + checksum = "6c50f3cdf47caa8d01f2be4a6663ea02418e892f9bbfd82c7b9a3a37eaccdd3a" 567 + dependencies = [ 568 + "aws-credential-types", 569 + "aws-smithy-async", 570 + "aws-smithy-runtime-api", 571 + "aws-smithy-types", 572 + "rustc_version", 573 + "tracing", 574 + ] 575 + 576 + [[package]] 188 577 name = "backtrace" 189 578 version = "0.3.76" 190 579 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 229 618 version = "0.22.1" 230 619 source = "registry+https://github.com/rust-lang/crates.io-index" 231 620 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 621 + 622 + [[package]] 623 + name = "base64-simd" 624 + version = "0.8.0" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" 627 + dependencies = [ 628 + "outref", 629 + "vsimd", 630 + ] 232 631 233 632 [[package]] 234 633 name = "bit-set" ··· 348 747 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 349 748 350 749 [[package]] 750 + name = "bytes-utils" 751 + version = "0.1.4" 752 + source = "registry+https://github.com/rust-lang/crates.io-index" 753 + checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" 754 + dependencies = [ 755 + "bytes", 756 + "either", 757 + ] 758 + 759 + [[package]] 351 760 name = "camino" 352 761 version = "1.2.2" 353 762 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 369 778 checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" 370 779 dependencies = [ 371 780 "find-msvc-tools", 781 + "jobserver", 782 + "libc", 372 783 "shlex", 373 784 ] 374 785 ··· 481 892 version = "0.7.5" 482 893 source = "registry+https://github.com/rust-lang/crates.io-index" 483 894 checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 895 + 896 + [[package]] 897 + name = "cmake" 898 + version = "0.1.58" 899 + source = "registry+https://github.com/rust-lang/crates.io-index" 900 + checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" 901 + dependencies = [ 902 + "cc", 903 + ] 484 904 485 905 [[package]] 486 906 name = "colorchoice" ··· 535 955 ] 536 956 537 957 [[package]] 958 + name = "core-foundation" 959 + version = "0.10.1" 960 + source = "registry+https://github.com/rust-lang/crates.io-index" 961 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 962 + dependencies = [ 963 + "core-foundation-sys", 964 + "libc", 965 + ] 966 + 967 + [[package]] 538 968 name = "core-foundation-sys" 539 969 version = "0.8.7" 540 970 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 681 1111 dependencies = [ 682 1112 "block-buffer 0.10.4", 683 1113 "crypto-common 0.1.6", 1114 + "subtle", 684 1115 ] 685 1116 686 1117 [[package]] ··· 726 1157 ] 727 1158 728 1159 [[package]] 1160 + name = "dunce" 1161 + version = "1.0.5" 1162 + source = "registry+https://github.com/rust-lang/crates.io-index" 1163 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 1164 + 1165 + [[package]] 1166 + name = "either" 1167 + version = "1.15.0" 1168 + source = "registry+https://github.com/rust-lang/crates.io-index" 1169 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 1170 + 1171 + [[package]] 729 1172 name = "encode_unicode" 730 1173 version = "1.0.0" 731 1174 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 826 1269 dependencies = [ 827 1270 "percent-encoding", 828 1271 ] 1272 + 1273 + [[package]] 1274 + name = "fs_extra" 1275 + version = "1.3.0" 1276 + source = "registry+https://github.com/rust-lang/crates.io-index" 1277 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 829 1278 830 1279 [[package]] 831 1280 name = "futures" ··· 963 1412 964 1413 [[package]] 965 1414 name = "h2" 1415 + version = "0.3.27" 1416 + source = "registry+https://github.com/rust-lang/crates.io-index" 1417 + checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" 1418 + dependencies = [ 1419 + "bytes", 1420 + "fnv", 1421 + "futures-core", 1422 + "futures-sink", 1423 + "futures-util", 1424 + "http 0.2.12", 1425 + "indexmap", 1426 + "slab", 1427 + "tokio", 1428 + "tokio-util", 1429 + "tracing", 1430 + ] 1431 + 1432 + [[package]] 1433 + name = "h2" 966 1434 version = "0.4.12" 967 1435 source = "registry+https://github.com/rust-lang/crates.io-index" 968 1436 checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" ··· 972 1440 "fnv", 973 1441 "futures-core", 974 1442 "futures-sink", 975 - "http", 1443 + "http 1.3.1", 976 1444 "indexmap", 977 1445 "slab", 978 1446 "tokio", ··· 1013 1481 version = "0.5.2" 1014 1482 source = "registry+https://github.com/rust-lang/crates.io-index" 1015 1483 checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1484 + 1485 + [[package]] 1486 + name = "hex" 1487 + version = "0.4.3" 1488 + source = "registry+https://github.com/rust-lang/crates.io-index" 1489 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1016 1490 1017 1491 [[package]] 1018 1492 name = "hex_fmt" ··· 1066 1540 ] 1067 1541 1068 1542 [[package]] 1543 + name = "hmac" 1544 + version = "0.12.1" 1545 + source = "registry+https://github.com/rust-lang/crates.io-index" 1546 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1547 + dependencies = [ 1548 + "digest 0.10.7", 1549 + ] 1550 + 1551 + [[package]] 1552 + name = "http" 1553 + version = "0.2.12" 1554 + source = "registry+https://github.com/rust-lang/crates.io-index" 1555 + checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 1556 + dependencies = [ 1557 + "bytes", 1558 + "fnv", 1559 + "itoa", 1560 + ] 1561 + 1562 + [[package]] 1069 1563 name = "http" 1070 1564 version = "1.3.1" 1071 1565 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1078 1572 1079 1573 [[package]] 1080 1574 name = "http-body" 1575 + version = "0.4.6" 1576 + source = "registry+https://github.com/rust-lang/crates.io-index" 1577 + checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 1578 + dependencies = [ 1579 + "bytes", 1580 + "http 0.2.12", 1581 + "pin-project-lite", 1582 + ] 1583 + 1584 + [[package]] 1585 + name = "http-body" 1081 1586 version = "1.0.1" 1082 1587 source = "registry+https://github.com/rust-lang/crates.io-index" 1083 1588 checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1084 1589 dependencies = [ 1085 1590 "bytes", 1086 - "http", 1591 + "http 1.3.1", 1087 1592 ] 1088 1593 1089 1594 [[package]] ··· 1094 1599 dependencies = [ 1095 1600 "bytes", 1096 1601 "futures-core", 1097 - "http", 1098 - "http-body", 1602 + "http 1.3.1", 1603 + "http-body 1.0.1", 1099 1604 "pin-project-lite", 1100 1605 ] 1101 1606 ··· 1122 1627 1123 1628 [[package]] 1124 1629 name = "hyper" 1630 + version = "0.14.32" 1631 + source = "registry+https://github.com/rust-lang/crates.io-index" 1632 + checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 1633 + dependencies = [ 1634 + "bytes", 1635 + "futures-channel", 1636 + "futures-core", 1637 + "futures-util", 1638 + "h2 0.3.27", 1639 + "http 0.2.12", 1640 + "http-body 0.4.6", 1641 + "httparse", 1642 + "httpdate", 1643 + "itoa", 1644 + "pin-project-lite", 1645 + "socket2 0.5.10", 1646 + "tokio", 1647 + "tower-service", 1648 + "tracing", 1649 + "want", 1650 + ] 1651 + 1652 + [[package]] 1653 + name = "hyper" 1125 1654 version = "1.7.0" 1126 1655 source = "registry+https://github.com/rust-lang/crates.io-index" 1127 1656 checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" ··· 1130 1659 "bytes", 1131 1660 "futures-channel", 1132 1661 "futures-core", 1133 - "h2", 1134 - "http", 1135 - "http-body", 1662 + "h2 0.4.12", 1663 + "http 1.3.1", 1664 + "http-body 1.0.1", 1136 1665 "httparse", 1137 1666 "httpdate", 1138 1667 "itoa", ··· 1145 1674 1146 1675 [[package]] 1147 1676 name = "hyper-rustls" 1677 + version = "0.24.2" 1678 + source = "registry+https://github.com/rust-lang/crates.io-index" 1679 + checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 1680 + dependencies = [ 1681 + "futures-util", 1682 + "http 0.2.12", 1683 + "hyper 0.14.32", 1684 + "log", 1685 + "rustls 0.21.12", 1686 + "tokio", 1687 + "tokio-rustls 0.24.1", 1688 + ] 1689 + 1690 + [[package]] 1691 + name = "hyper-rustls" 1148 1692 version = "0.27.7" 1149 1693 source = "registry+https://github.com/rust-lang/crates.io-index" 1150 1694 checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1151 1695 dependencies = [ 1152 - "http", 1153 - "hyper", 1696 + "http 1.3.1", 1697 + "hyper 1.7.0", 1154 1698 "hyper-util", 1155 - "rustls", 1699 + "rustls 0.23.32", 1700 + "rustls-native-certs", 1156 1701 "rustls-pki-types", 1157 1702 "tokio", 1158 - "tokio-rustls", 1703 + "tokio-rustls 0.26.4", 1159 1704 "tower-service", 1160 1705 ] 1161 1706 ··· 1167 1712 dependencies = [ 1168 1713 "bytes", 1169 1714 "http-body-util", 1170 - "hyper", 1715 + "hyper 1.7.0", 1171 1716 "hyper-util", 1172 1717 "native-tls", 1173 1718 "tokio", ··· 1186 1731 "futures-channel", 1187 1732 "futures-core", 1188 1733 "futures-util", 1189 - "http", 1190 - "http-body", 1191 - "hyper", 1734 + "http 1.3.1", 1735 + "http-body 1.0.1", 1736 + "hyper 1.7.0", 1192 1737 "ipnet", 1193 1738 "libc", 1194 1739 "percent-encoding", ··· 1458 2003 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1459 2004 1460 2005 [[package]] 2006 + name = "jobserver" 2007 + version = "0.1.34" 2008 + source = "registry+https://github.com/rust-lang/crates.io-index" 2009 + checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" 2010 + dependencies = [ 2011 + "getrandom 0.3.3", 2012 + "libc", 2013 + ] 2014 + 2015 + [[package]] 1461 2016 name = "js-sys" 1462 2017 version = "0.3.81" 1463 2018 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1779 2334 ] 1780 2335 1781 2336 [[package]] 2337 + name = "mlf-dns-route53" 2338 + version = "0.1.0" 2339 + dependencies = [ 2340 + "aws-config", 2341 + "aws-credential-types", 2342 + "aws-sdk-route53", 2343 + "mlf-plugin-host", 2344 + "serde", 2345 + "serde_json", 2346 + "thiserror 2.0.17", 2347 + "tokio", 2348 + ] 2349 + 2350 + [[package]] 1782 2351 name = "mlf-integration-tests" 1783 2352 version = "0.1.0" 1784 2353 dependencies = [ ··· 1974 2543 "libc", 1975 2544 "log", 1976 2545 "openssl", 1977 - "openssl-probe", 2546 + "openssl-probe 0.1.6", 1978 2547 "openssl-sys", 1979 2548 "schannel", 1980 - "security-framework", 2549 + "security-framework 2.11.1", 1981 2550 "security-framework-sys", 1982 2551 "tempfile", 1983 2552 ] ··· 2024 2593 version = "0.1.0" 2025 2594 source = "registry+https://github.com/rust-lang/crates.io-index" 2026 2595 checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 2596 + 2597 + [[package]] 2598 + name = "num-integer" 2599 + version = "0.1.46" 2600 + source = "registry+https://github.com/rust-lang/crates.io-index" 2601 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 2602 + dependencies = [ 2603 + "num-traits", 2604 + ] 2027 2605 2028 2606 [[package]] 2029 2607 name = "num-traits" ··· 2098 2676 checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 2099 2677 2100 2678 [[package]] 2679 + name = "openssl-probe" 2680 + version = "0.2.1" 2681 + source = "registry+https://github.com/rust-lang/crates.io-index" 2682 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 2683 + 2684 + [[package]] 2101 2685 name = "openssl-sys" 2102 2686 version = "0.9.109" 2103 2687 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2114 2698 version = "0.2.0" 2115 2699 source = "registry+https://github.com/rust-lang/crates.io-index" 2116 2700 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 2701 + 2702 + [[package]] 2703 + name = "outref" 2704 + version = "0.5.2" 2705 + source = "registry+https://github.com/rust-lang/crates.io-index" 2706 + checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" 2117 2707 2118 2708 [[package]] 2119 2709 name = "owo-colors" ··· 2349 2939 ] 2350 2940 2351 2941 [[package]] 2942 + name = "regex-lite" 2943 + version = "0.1.9" 2944 + source = "registry+https://github.com/rust-lang/crates.io-index" 2945 + checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" 2946 + 2947 + [[package]] 2352 2948 name = "regex-syntax" 2353 2949 version = "0.8.6" 2354 2950 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2364 2960 "bytes", 2365 2961 "encoding_rs", 2366 2962 "futures-core", 2367 - "h2", 2368 - "http", 2369 - "http-body", 2963 + "h2 0.4.12", 2964 + "http 1.3.1", 2965 + "http-body 1.0.1", 2370 2966 "http-body-util", 2371 - "hyper", 2372 - "hyper-rustls", 2967 + "hyper 1.7.0", 2968 + "hyper-rustls 0.27.7", 2373 2969 "hyper-tls", 2374 2970 "hyper-util", 2375 2971 "js-sys", ··· 2430 3026 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 2431 3027 2432 3028 [[package]] 3029 + name = "rustc_version" 3030 + version = "0.4.1" 3031 + source = "registry+https://github.com/rust-lang/crates.io-index" 3032 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 3033 + dependencies = [ 3034 + "semver", 3035 + ] 3036 + 3037 + [[package]] 2433 3038 name = "rustix" 2434 3039 version = "1.1.2" 2435 3040 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2444 3049 2445 3050 [[package]] 2446 3051 name = "rustls" 3052 + version = "0.21.12" 3053 + source = "registry+https://github.com/rust-lang/crates.io-index" 3054 + checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 3055 + dependencies = [ 3056 + "log", 3057 + "ring", 3058 + "rustls-webpki 0.101.7", 3059 + "sct", 3060 + ] 3061 + 3062 + [[package]] 3063 + name = "rustls" 2447 3064 version = "0.23.32" 2448 3065 source = "registry+https://github.com/rust-lang/crates.io-index" 2449 3066 checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" 2450 3067 dependencies = [ 3068 + "aws-lc-rs", 2451 3069 "once_cell", 2452 3070 "rustls-pki-types", 2453 - "rustls-webpki", 3071 + "rustls-webpki 0.103.7", 2454 3072 "subtle", 2455 3073 "zeroize", 2456 3074 ] 2457 3075 2458 3076 [[package]] 3077 + name = "rustls-native-certs" 3078 + version = "0.8.3" 3079 + source = "registry+https://github.com/rust-lang/crates.io-index" 3080 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 3081 + dependencies = [ 3082 + "openssl-probe 0.2.1", 3083 + "rustls-pki-types", 3084 + "schannel", 3085 + "security-framework 3.5.1", 3086 + ] 3087 + 3088 + [[package]] 2459 3089 name = "rustls-pki-types" 2460 3090 version = "1.12.0" 2461 3091 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2466 3096 2467 3097 [[package]] 2468 3098 name = "rustls-webpki" 3099 + version = "0.101.7" 3100 + source = "registry+https://github.com/rust-lang/crates.io-index" 3101 + checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 3102 + dependencies = [ 3103 + "ring", 3104 + "untrusted", 3105 + ] 3106 + 3107 + [[package]] 3108 + name = "rustls-webpki" 2469 3109 version = "0.103.7" 2470 3110 source = "registry+https://github.com/rust-lang/crates.io-index" 2471 3111 checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" 2472 3112 dependencies = [ 3113 + "aws-lc-rs", 2473 3114 "ring", 2474 3115 "rustls-pki-types", 2475 3116 "untrusted", ··· 2512 3153 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2513 3154 2514 3155 [[package]] 3156 + name = "sct" 3157 + version = "0.7.1" 3158 + source = "registry+https://github.com/rust-lang/crates.io-index" 3159 + checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 3160 + dependencies = [ 3161 + "ring", 3162 + "untrusted", 3163 + ] 3164 + 3165 + [[package]] 2515 3166 name = "security-framework" 2516 3167 version = "2.11.1" 2517 3168 source = "registry+https://github.com/rust-lang/crates.io-index" 2518 3169 checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2519 3170 dependencies = [ 2520 3171 "bitflags 2.9.4", 2521 - "core-foundation", 3172 + "core-foundation 0.9.4", 3173 + "core-foundation-sys", 3174 + "libc", 3175 + "security-framework-sys", 3176 + ] 3177 + 3178 + [[package]] 3179 + name = "security-framework" 3180 + version = "3.5.1" 3181 + source = "registry+https://github.com/rust-lang/crates.io-index" 3182 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 3183 + dependencies = [ 3184 + "bitflags 2.9.4", 3185 + "core-foundation 0.10.1", 2522 3186 "core-foundation-sys", 2523 3187 "libc", 2524 3188 "security-framework-sys", ··· 2533 3197 "core-foundation-sys", 2534 3198 "libc", 2535 3199 ] 3200 + 3201 + [[package]] 3202 + name = "semver" 3203 + version = "1.0.28" 3204 + source = "registry+https://github.com/rust-lang/crates.io-index" 3205 + checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" 2536 3206 2537 3207 [[package]] 2538 3208 name = "serde" ··· 2867 3537 checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2868 3538 dependencies = [ 2869 3539 "bitflags 2.9.4", 2870 - "core-foundation", 3540 + "core-foundation 0.9.4", 2871 3541 "system-configuration-sys", 2872 3542 ] 2873 3543 ··· 3061 3731 3062 3732 [[package]] 3063 3733 name = "tokio-rustls" 3734 + version = "0.24.1" 3735 + source = "registry+https://github.com/rust-lang/crates.io-index" 3736 + checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 3737 + dependencies = [ 3738 + "rustls 0.21.12", 3739 + "tokio", 3740 + ] 3741 + 3742 + [[package]] 3743 + name = "tokio-rustls" 3064 3744 version = "0.26.4" 3065 3745 source = "registry+https://github.com/rust-lang/crates.io-index" 3066 3746 checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 3067 3747 dependencies = [ 3068 - "rustls", 3748 + "rustls 0.23.32", 3069 3749 "tokio", 3070 3750 ] 3071 3751 ··· 3191 3871 "bitflags 2.9.4", 3192 3872 "bytes", 3193 3873 "futures-util", 3194 - "http", 3195 - "http-body", 3874 + "http 1.3.1", 3875 + "http-body 1.0.1", 3196 3876 "iri-string", 3197 3877 "pin-project-lite", 3198 3878 "tower 0.5.2", ··· 3403 4083 ] 3404 4084 3405 4085 [[package]] 4086 + name = "urlencoding" 4087 + version = "2.1.3" 4088 + source = "registry+https://github.com/rust-lang/crates.io-index" 4089 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 4090 + 4091 + [[package]] 3406 4092 name = "utf8_iter" 3407 4093 version = "1.0.4" 3408 4094 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3415 4101 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3416 4102 3417 4103 [[package]] 4104 + name = "uuid" 4105 + version = "1.23.1" 4106 + source = "registry+https://github.com/rust-lang/crates.io-index" 4107 + checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" 4108 + dependencies = [ 4109 + "js-sys", 4110 + "wasm-bindgen", 4111 + ] 4112 + 4113 + [[package]] 3418 4114 name = "valuable" 3419 4115 version = "0.1.1" 3420 4116 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3431 4127 version = "0.9.5" 3432 4128 source = "registry+https://github.com/rust-lang/crates.io-index" 3433 4129 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4130 + 4131 + [[package]] 4132 + name = "vsimd" 4133 + version = "0.8.0" 4134 + source = "registry+https://github.com/rust-lang/crates.io-index" 4135 + checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" 3434 4136 3435 4137 [[package]] 3436 4138 name = "walkdir" ··· 3959 4661 "base64", 3960 4662 "deadpool", 3961 4663 "futures", 3962 - "http", 4664 + "http 1.3.1", 3963 4665 "http-body-util", 3964 - "hyper", 4666 + "hyper 1.7.0", 3965 4667 "hyper-util", 3966 4668 "log", 3967 4669 "once_cell", ··· 3983 4685 version = "0.6.1" 3984 4686 source = "registry+https://github.com/rust-lang/crates.io-index" 3985 4687 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 4688 + 4689 + [[package]] 4690 + name = "xmlparser" 4691 + version = "0.13.6" 4692 + source = "registry+https://github.com/rust-lang/crates.io-index" 4693 + checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" 3986 4694 3987 4695 [[package]] 3988 4696 name = "yoke"
+1
Cargo.toml
··· 7 7 "dns-plugins/mlf-dns-cloudflare", 8 8 "mlf-atproto", 9 9 "mlf-cli", 10 + "dns-plugins/mlf-dns-route53", 10 11 "mlf-plugin-host", 11 12 "mlf-publish", 12 13 "mlf-codegen",
+20
dns-plugins/mlf-dns-route53/Cargo.toml
··· 1 + [package] 2 + name = "mlf-dns-route53" 3 + version = "0.1.0" 4 + edition = "2024" 5 + license = "MIT" 6 + description = "Official MLF DNS provider plugin for AWS Route 53" 7 + 8 + [[bin]] 9 + name = "mlf-dns-route53" 10 + path = "src/main.rs" 11 + 12 + [dependencies] 13 + mlf-plugin-host = { path = "../../mlf-plugin-host" } 14 + aws-config = { version = "1", features = ["behavior-version-latest"] } 15 + aws-credential-types = "1" 16 + aws-sdk-route53 = "1" 17 + serde = { version = "1", features = ["derive"] } 18 + serde_json = "1" 19 + thiserror = "2" 20 + tokio = { version = "1", features = ["io-util", "macros", "rt"] }
+283
dns-plugins/mlf-dns-route53/src/api.rs
··· 1 + //! Thin Route 53 client wrapping `aws-sdk-route53` for the plugin's 2 + //! four ops: zone lookup by DNS name, TXT list/upsert/delete. 3 + //! 4 + //! Route 53 doesn't have per-record IDs, so the plugin uses the 5 + //! record's (name, type) pair as the identity and treats "upsert" as 6 + //! a single UPSERT change. 7 + 8 + use crate::Credentials; 9 + use aws_credential_types::Credentials as AwsCredentials; 10 + use aws_sdk_route53::Client; 11 + use aws_sdk_route53::config::Region; 12 + use aws_sdk_route53::types::{ 13 + Change, ChangeAction, ChangeBatch, ResourceRecord, ResourceRecordSet, RrType, 14 + }; 15 + use thiserror::Error; 16 + 17 + #[derive(Error, Debug)] 18 + pub enum Route53Error { 19 + #[error("Route 53 error: {0}")] 20 + Api(String), 21 + } 22 + 23 + pub struct Route53Client { 24 + client: Client, 25 + } 26 + 27 + impl Route53Client { 28 + pub async fn new(creds: &Credentials) -> Self { 29 + let region = Region::new(creds.region.clone()); 30 + let aws_creds = AwsCredentials::new( 31 + creds.access_key.clone(), 32 + creds.secret_key.clone(), 33 + creds.session_token.clone(), 34 + None, 35 + "mlf-dns-route53", 36 + ); 37 + let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) 38 + .region(region) 39 + .credentials_provider(aws_creds) 40 + .load() 41 + .await; 42 + Self { 43 + client: Client::new(&config), 44 + } 45 + } 46 + 47 + /// Sanity-check the credentials by listing hosted zones (a cheap, 48 + /// permission-scoped call). Returns a short human-friendly label — 49 + /// AWS account alias would be nicer but needs an extra API call. 50 + pub async fn verify(&self) -> Result<String, Route53Error> { 51 + let resp = self 52 + .client 53 + .list_hosted_zones() 54 + .max_items(1) 55 + .send() 56 + .await 57 + .map_err(|e| Route53Error::Api(format!("{e}")))?; 58 + let count = resp.hosted_zones.len(); 59 + Ok(format!("route53 ({count} zone(s) visible)")) 60 + } 61 + 62 + pub async fn find_zone_for(&self, name: &str) -> Result<Option<Zone>, Route53Error> { 63 + let stripped = name.strip_prefix("_lexicon.").unwrap_or(name); 64 + for candidate in parent_domains(stripped) { 65 + // `list_hosted_zones_by_name` returns zones at or after the 66 + // name in lexicographic order, so the first match (if any) 67 + // is our best candidate. 68 + let resp = self 69 + .client 70 + .list_hosted_zones_by_name() 71 + .dns_name(&candidate) 72 + .max_items(1) 73 + .send() 74 + .await 75 + .map_err(|e| Route53Error::Api(format!("{e}")))?; 76 + for zone in resp.hosted_zones { 77 + // AWS returns the zone name with a trailing dot. 78 + let zone_name = zone.name.trim_end_matches('.'); 79 + if zone_name == candidate { 80 + return Ok(Some(Zone { 81 + id: zone.id.trim_start_matches("/hostedzone/").to_string(), 82 + name: zone_name.to_string(), 83 + })); 84 + } 85 + } 86 + } 87 + Ok(None) 88 + } 89 + 90 + pub async fn list_txt( 91 + &self, 92 + zone_id: &str, 93 + name: &str, 94 + ) -> Result<Vec<TxtRecord>, Route53Error> { 95 + let name_with_dot = if name.ends_with('.') { 96 + name.to_string() 97 + } else { 98 + format!("{name}.") 99 + }; 100 + let resp = self 101 + .client 102 + .list_resource_record_sets() 103 + .hosted_zone_id(zone_id) 104 + .start_record_name(&name_with_dot) 105 + .start_record_type(RrType::Txt) 106 + .max_items(1) 107 + .send() 108 + .await 109 + .map_err(|e| Route53Error::Api(format!("{e}")))?; 110 + let mut out = Vec::new(); 111 + for rrset in resp.resource_record_sets { 112 + if rrset.r#type != RrType::Txt { 113 + continue; 114 + } 115 + if rrset.name.trim_end_matches('.') != name.trim_end_matches('.') { 116 + continue; 117 + } 118 + // Route 53 has no per-record id; (name, type) is the identity. 119 + // We surface the name as the `id` so the host can round-trip it. 120 + for rr in rrset.resource_records.unwrap_or_default() { 121 + out.push(TxtRecord { 122 + id: rrset.name.clone(), 123 + value: unquote(&rr.value), 124 + }); 125 + } 126 + } 127 + Ok(out) 128 + } 129 + 130 + /// Create or update the TXT at `name` to hold a single value. 131 + pub async fn upsert_txt( 132 + &self, 133 + zone_id: &str, 134 + name: &str, 135 + value: &str, 136 + ttl: u32, 137 + ) -> Result<String, Route53Error> { 138 + let quoted = format!("\"{}\"", escape_for_txt(value)); 139 + let rr = ResourceRecord::builder() 140 + .value(&quoted) 141 + .build() 142 + .map_err(|e| Route53Error::Api(e.to_string()))?; 143 + let rrset = ResourceRecordSet::builder() 144 + .name(name) 145 + .r#type(RrType::Txt) 146 + .ttl(ttl as i64) 147 + .resource_records(rr) 148 + .build() 149 + .map_err(|e| Route53Error::Api(e.to_string()))?; 150 + let change = Change::builder() 151 + .action(ChangeAction::Upsert) 152 + .resource_record_set(rrset) 153 + .build() 154 + .map_err(|e| Route53Error::Api(e.to_string()))?; 155 + let batch = ChangeBatch::builder() 156 + .changes(change) 157 + .build() 158 + .map_err(|e| Route53Error::Api(e.to_string()))?; 159 + self.client 160 + .change_resource_record_sets() 161 + .hosted_zone_id(zone_id) 162 + .change_batch(batch) 163 + .send() 164 + .await 165 + .map_err(|e| Route53Error::Api(format!("{e}")))?; 166 + Ok(name.to_string()) 167 + } 168 + 169 + pub async fn delete_txt(&self, zone_id: &str, name: &str) -> Result<(), Route53Error> { 170 + // Need current record values for the delete; skip cleanly if absent. 171 + let existing = self.list_txt(zone_id, name).await?; 172 + if existing.is_empty() { 173 + return Ok(()); 174 + } 175 + let mut rr_builder = ResourceRecordSet::builder() 176 + .name(name) 177 + .r#type(RrType::Txt) 178 + .ttl(300); 179 + for rr in &existing { 180 + let quoted = format!("\"{}\"", escape_for_txt(&rr.value)); 181 + rr_builder = rr_builder.resource_records( 182 + ResourceRecord::builder() 183 + .value(&quoted) 184 + .build() 185 + .map_err(|e| Route53Error::Api(e.to_string()))?, 186 + ); 187 + } 188 + let rrset = rr_builder 189 + .build() 190 + .map_err(|e| Route53Error::Api(e.to_string()))?; 191 + let change = Change::builder() 192 + .action(ChangeAction::Delete) 193 + .resource_record_set(rrset) 194 + .build() 195 + .map_err(|e| Route53Error::Api(e.to_string()))?; 196 + let batch = ChangeBatch::builder() 197 + .changes(change) 198 + .build() 199 + .map_err(|e| Route53Error::Api(e.to_string()))?; 200 + self.client 201 + .change_resource_record_sets() 202 + .hosted_zone_id(zone_id) 203 + .change_batch(batch) 204 + .send() 205 + .await 206 + .map_err(|e| Route53Error::Api(format!("{e}")))?; 207 + Ok(()) 208 + } 209 + } 210 + 211 + #[derive(Debug, Clone)] 212 + pub struct Zone { 213 + pub id: String, 214 + #[allow(dead_code)] 215 + pub name: String, 216 + } 217 + 218 + #[derive(Debug, Clone)] 219 + pub struct TxtRecord { 220 + pub id: String, 221 + pub value: String, 222 + } 223 + 224 + fn parent_domains(name: &str) -> Vec<String> { 225 + let parts: Vec<&str> = name.split('.').collect(); 226 + (0..parts.len()).map(|i| parts[i..].join(".")).collect() 227 + } 228 + 229 + fn escape_for_txt(s: &str) -> String { 230 + s.replace('\\', "\\\\").replace('"', "\\\"") 231 + } 232 + 233 + fn unquote(s: &str) -> String { 234 + // Route 53 surrounds TXT record values in double quotes and escapes 235 + // internal quotes/backslashes. Strip exactly one set of outer quotes 236 + // — using `trim_matches` would eat part of an escape sequence like 237 + // `\"` at the end of the string. 238 + let inner = s 239 + .strip_prefix('"') 240 + .and_then(|t| t.strip_suffix('"')) 241 + .unwrap_or(s); 242 + // Unescape: walk once so `\\"` → `\"` stays quoted, not unescaped twice. 243 + let mut out = String::with_capacity(inner.len()); 244 + let mut chars = inner.chars(); 245 + while let Some(c) = chars.next() { 246 + if c == '\\' { 247 + match chars.next() { 248 + Some('\\') => out.push('\\'), 249 + Some('"') => out.push('"'), 250 + Some(other) => { 251 + out.push('\\'); 252 + out.push(other); 253 + } 254 + None => out.push('\\'), 255 + } 256 + } else { 257 + out.push(c); 258 + } 259 + } 260 + out 261 + } 262 + 263 + #[cfg(test)] 264 + mod tests { 265 + use super::*; 266 + 267 + #[test] 268 + fn parent_domains_walks_up() { 269 + assert_eq!( 270 + parent_domains("forum.example.com"), 271 + vec!["forum.example.com", "example.com", "com"] 272 + ); 273 + } 274 + 275 + #[test] 276 + fn txt_escaping_round_trips() { 277 + let raw = r#"did=did:plc:"hello""#; 278 + let escaped = escape_for_txt(raw); 279 + assert!(escaped.contains("\\\"")); 280 + let roundtrip = unquote(&format!("\"{escaped}\"")); 281 + assert_eq!(roundtrip, raw); 282 + } 283 + }
+304
dns-plugins/mlf-dns-route53/src/main.rs
··· 1 + //! Official MLF DNS provider plugin for AWS Route 53. 2 + //! 3 + //! Options schema: 4 + //! - `access_key` (secret, required) — AWS access key ID 5 + //! - `secret_key` (secret, required) — AWS secret access key 6 + //! - `region` (non-secret, optional, default `us-east-1`) — Route 53 7 + //! is a global service but an SDK region is required for signing. 8 + //! - `session_token` (secret, optional) — for STS-issued temporary 9 + //! credentials. 10 + //! 11 + //! Each op walks parent domains to find the matching hosted zone, 12 + //! then performs the corresponding ChangeResourceRecordSets or 13 + //! ListResourceRecordSets call. 14 + 15 + mod api; 16 + 17 + use api::{Route53Client, Route53Error}; 18 + use mlf_plugin_host::plugin::{Server, empty_data, params_as}; 19 + use mlf_plugin_host::protocol::{HelloData, OptionField, PROTOCOL_VERSION, Request}; 20 + use serde::{Deserialize, Serialize}; 21 + use serde_json::{Value, json}; 22 + 23 + #[tokio::main(flavor = "current_thread")] 24 + async fn main() -> std::io::Result<()> { 25 + let mut server = Server::stdio(); 26 + 27 + let identity = HelloData { 28 + name: "route53".into(), 29 + protocol_version: PROTOCOL_VERSION, 30 + kind: Some("dns".into()), 31 + capabilities: vec![ 32 + "login".into(), 33 + "list_txt".into(), 34 + "upsert_txt".into(), 35 + "delete_txt".into(), 36 + "resolve_zone".into(), 37 + ], 38 + options_schema: vec![ 39 + OptionField { 40 + name: "access_key".into(), 41 + label: "AWS access key ID".into(), 42 + help: Some( 43 + "An IAM access key with route53:ListHostedZonesByName, \ 44 + route53:ListResourceRecordSets, and \ 45 + route53:ChangeResourceRecordSets on the relevant zones." 46 + .into(), 47 + ), 48 + secret: true, 49 + required: true, 50 + default: None, 51 + }, 52 + OptionField { 53 + name: "secret_key".into(), 54 + label: "AWS secret access key".into(), 55 + help: None, 56 + secret: true, 57 + required: true, 58 + default: None, 59 + }, 60 + OptionField { 61 + name: "session_token".into(), 62 + label: "AWS session token (for STS / temporary credentials)".into(), 63 + help: None, 64 + secret: true, 65 + required: false, 66 + default: None, 67 + }, 68 + OptionField { 69 + name: "region".into(), 70 + label: "AWS region".into(), 71 + help: Some( 72 + "Route 53 is global, but the SDK needs a region for signing. \ 73 + `us-east-1` is a safe default." 74 + .into(), 75 + ), 76 + secret: false, 77 + required: false, 78 + default: Some(Value::String("us-east-1".into())), 79 + }, 80 + ], 81 + }; 82 + 83 + if server.handshake(identity).await.is_err() { 84 + return Ok(()); 85 + } 86 + 87 + let mut creds: Option<Credentials> = None; 88 + 89 + while let Ok(Some(req)) = server.next_request().await { 90 + if let Err(e) = dispatch(&mut server, &req, &mut creds).await { 91 + let _ = server.reply_err("internal", &e.to_string(), false).await; 92 + } 93 + } 94 + 95 + Ok(()) 96 + } 97 + 98 + #[derive(Debug, Clone, Serialize, Deserialize)] 99 + struct Credentials { 100 + access_key: String, 101 + secret_key: String, 102 + #[serde(default)] 103 + session_token: Option<String>, 104 + #[serde(default = "default_region")] 105 + region: String, 106 + } 107 + 108 + fn default_region() -> String { 109 + "us-east-1".to_string() 110 + } 111 + 112 + #[derive(Debug, Deserialize)] 113 + struct InitParams { 114 + #[serde(default)] 115 + credentials: Option<Credentials>, 116 + } 117 + 118 + #[derive(Debug, Deserialize)] 119 + struct ResolveZoneParams { 120 + domain: String, 121 + } 122 + 123 + #[derive(Debug, Deserialize)] 124 + struct ListTxtParams { 125 + name: String, 126 + } 127 + 128 + #[derive(Debug, Deserialize)] 129 + struct UpsertTxtParams { 130 + name: String, 131 + value: String, 132 + #[serde(default)] 133 + ttl: Option<u32>, 134 + } 135 + 136 + #[derive(Debug, Deserialize)] 137 + struct DeleteTxtParams { 138 + name: String, 139 + #[allow(dead_code)] 140 + record_id: String, 141 + } 142 + 143 + #[derive(thiserror::Error, Debug)] 144 + enum DispatchError { 145 + #[error("{0}")] 146 + Plugin(#[from] mlf_plugin_host::plugin::PluginError), 147 + #[error("{0}")] 148 + Route53(#[from] Route53Error), 149 + } 150 + 151 + async fn dispatch<W, R>( 152 + server: &mut Server<W, R>, 153 + req: &Request, 154 + creds: &mut Option<Credentials>, 155 + ) -> Result<(), DispatchError> 156 + where 157 + W: tokio::io::AsyncWrite + Unpin, 158 + R: tokio::io::AsyncBufReadExt + Unpin, 159 + { 160 + match req.op.as_str() { 161 + "init" => { 162 + let InitParams { credentials } = params_as(req)?; 163 + *creds = credentials; 164 + server.reply_ok(empty_data()).await?; 165 + } 166 + "login" => { 167 + let Some(c) = creds.as_ref() else { 168 + server 169 + .reply_err( 170 + "no_credentials", 171 + "login called before init set credentials", 172 + false, 173 + ) 174 + .await?; 175 + return Ok(()); 176 + }; 177 + match Route53Client::new(c).await.verify().await { 178 + Ok(name) => { 179 + server 180 + .reply_ok(json!({ 181 + "credentials": c, 182 + "display_name": name, 183 + })) 184 + .await?; 185 + } 186 + Err(e) => { 187 + server 188 + .reply_err("invalid_credentials", &e.to_string(), false) 189 + .await?; 190 + } 191 + } 192 + } 193 + "logout" => { 194 + *creds = None; 195 + server.reply_ok(empty_data()).await?; 196 + } 197 + "resolve_zone" => { 198 + let ResolveZoneParams { domain } = params_as(req)?; 199 + let c = require_creds(server, creds).await?; 200 + let client = Route53Client::new(&c).await; 201 + match client.find_zone_for(&domain).await? { 202 + Some(zone) => { 203 + server 204 + .reply_ok(json!({ 205 + "zone_id": zone.id, 206 + "covered": true, 207 + })) 208 + .await?; 209 + } 210 + None => { 211 + server 212 + .reply_ok(json!({"zone_id": Value::Null, "covered": false})) 213 + .await?; 214 + } 215 + } 216 + } 217 + "list_txt" => { 218 + let ListTxtParams { name } = params_as(req)?; 219 + let c = require_creds(server, creds).await?; 220 + let client = Route53Client::new(&c).await; 221 + let zone = match client.find_zone_for(&name).await? { 222 + Some(z) => z, 223 + None => { 224 + server 225 + .reply_err("unknown_zone", &format!("no zone covers {name}"), false) 226 + .await?; 227 + return Ok(()); 228 + } 229 + }; 230 + let records = client.list_txt(&zone.id, &name).await?; 231 + server 232 + .reply_ok(json!({ 233 + "records": records.into_iter().map(|r| json!({ 234 + "id": r.id, 235 + "value": r.value, 236 + })).collect::<Vec<_>>(), 237 + })) 238 + .await?; 239 + } 240 + "upsert_txt" => { 241 + let UpsertTxtParams { name, value, ttl } = params_as(req)?; 242 + let c = require_creds(server, creds).await?; 243 + let client = Route53Client::new(&c).await; 244 + let zone = match client.find_zone_for(&name).await? { 245 + Some(z) => z, 246 + None => { 247 + server 248 + .reply_err("unknown_zone", &format!("no zone covers {name}"), false) 249 + .await?; 250 + return Ok(()); 251 + } 252 + }; 253 + let id = client 254 + .upsert_txt(&zone.id, &name, &value, ttl.unwrap_or(300)) 255 + .await?; 256 + server.reply_ok(json!({ "record_id": id })).await?; 257 + } 258 + "delete_txt" => { 259 + let DeleteTxtParams { name, record_id: _ } = params_as(req)?; 260 + let c = require_creds(server, creds).await?; 261 + let client = Route53Client::new(&c).await; 262 + let zone = match client.find_zone_for(&name).await? { 263 + Some(z) => z, 264 + None => { 265 + server 266 + .reply_err("unknown_zone", &format!("no zone covers {name}"), false) 267 + .await?; 268 + return Ok(()); 269 + } 270 + }; 271 + client.delete_txt(&zone.id, &name).await?; 272 + server.reply_ok(empty_data()).await?; 273 + } 274 + other => { 275 + server 276 + .reply_err("unknown_op", &format!("unsupported op `{other}`"), false) 277 + .await?; 278 + } 279 + } 280 + Ok(()) 281 + } 282 + 283 + async fn require_creds<W, R>( 284 + server: &mut Server<W, R>, 285 + creds: &Option<Credentials>, 286 + ) -> Result<Credentials, DispatchError> 287 + where 288 + W: tokio::io::AsyncWrite + Unpin, 289 + R: tokio::io::AsyncBufReadExt + Unpin, 290 + { 291 + if let Some(c) = creds.clone() { 292 + return Ok(c); 293 + } 294 + server 295 + .reply_err( 296 + "no_credentials", 297 + "host hasn't called init with credentials yet", 298 + false, 299 + ) 300 + .await?; 301 + Err(DispatchError::Plugin( 302 + mlf_plugin_host::plugin::PluginError::Unexpected("missing credentials".into()), 303 + )) 304 + }