Giter Site home page Giter Site logo

joohoi / acme-dns Goto Github PK

View Code? Open in Web Editor NEW
2.0K 45.0 222.0 25.86 MB

Limited DNS server with RESTful HTTP API to handle ACME DNS challenges easily and securely.

License: MIT License

Go 98.55% Shell 0.71% Dockerfile 0.74%
acme-challenge acme-dns dns-server tls-certificate letsencrypt

acme-dns's Introduction

Build Status Coverage Status Go Report Card

acme-dns

A simplified DNS server with a RESTful HTTP API to provide a simple way to automate ACME DNS challenges.

Why?

Many DNS servers do not provide an API to enable automation for the ACME DNS challenges. Those which do, give the keys way too much power. Leaving the keys laying around your random boxes is too often a requirement to have a meaningful process automation.

Acme-dns provides a simple API exclusively for TXT record updates and should be used with ACME magic "_acme-challenge" - subdomain CNAME records. This way, in the unfortunate exposure of API keys, the effects are limited to the subdomain TXT record in question.

So basically it boils down to accessibility and security.

For longer explanation of the underlying issue and other proposed solutions, see a blog post on the topic from EFF deeplinks blog: https://www.eff.org/deeplinks/2018/02/technical-deep-dive-securing-automation-acme-dns-challenge-validation

Features

  • Simplified DNS server, serving your ACME DNS challenges (TXT)
  • Custom records (have your required A, AAAA, NS, etc. records served)
  • HTTP API automatically acquires and uses Let's Encrypt TLS certificate
  • Limit /update API endpoint access to specific CIDR mask(s), defined in the /register request
  • Supports SQLite & PostgreSQL as DB backends
  • Rolling update of two TXT records to be able to answer to challenges for certificates that have both names: yourdomain.tld and *.yourdomain.tld, as both of the challenges point to the same subdomain.
  • Simple deployment (it's Go after all)

Usage

A client application for acme-dns with support for Certbot authentication hooks is available at: https://github.com/acme-dns/acme-dns-client.

asciicast

Using acme-dns is a three-step process (provided you already have the self-hosted server set up):

  • Get credentials and unique subdomain (simple POST request to eg. https://auth.acme-dns.io/register)
  • Create a (ACME magic) CNAME record to your existing zone, pointing to the subdomain you got from the registration. (eg. _acme-challenge.domainiwantcertfor.tld. CNAME a097455b-52cc-4569-90c8-7a4b97c6eba8.auth.example.org )
  • Use your credentials to POST new DNS challenge values to an acme-dns server for the CA to validate from.
  • Crontab and forget.

API

Register endpoint

The method returns a new unique subdomain and credentials needed to update your record. Fulldomain is where you can point your own _acme-challenge subdomain CNAME record to. With the credentials, you can update the TXT response in the service to match the challenge token, later referred as ___validation_token_received_from_the_ca___, given out by the Certificate Authority.

Optional:: You can POST JSON data to limit the /update requests to predefined source networks using CIDR notation.

POST /register

OPTIONAL Example input

{
    "allowfrom": [
        "192.168.100.1/24",
        "1.2.3.4/32",
        "2002:c0a8:2a00::0/40"
    ]
}

Status: 201 Created

{
    "allowfrom": [
        "192.168.100.1/24",
        "1.2.3.4/32",
        "2002:c0a8:2a00::0/40"
    ],
    "fulldomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a.auth.acme-dns.io",
    "password": "htB9mR9DYgcu9bX_afHF62erXaH2TS7bg9KW3F7Z",
    "subdomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a",
    "username": "c36f50e8-4632-44f0-83fe-e070fef28a10"
}

Update endpoint

The method allows you to update the TXT answer contents of your unique subdomain. Usually carried automatically by automated ACME client.

POST /update

Required headers

Header name Description Example
X-Api-User UUIDv4 username received from registration X-Api-User: c36f50e8-4632-44f0-83fe-e070fef28a10
X-Api-Key Password received from registration X-Api-Key: htB9mR9DYgcu9bX_afHF62erXaH2TS7bg9KW3F7Z

Example input

{
    "subdomain": "8e5700ea-a4bf-41c7-8a77-e990661dcc6a",
    "txt": "___validation_token_received_from_the_ca___"
}

Response

Status: 200 OK

{
    "txt": "___validation_token_received_from_the_ca___"
}

Health check endpoint

The method can be used to check readiness and/or liveness of the server. It will return status code 200 on success or won't be reachable.

GET /health

Self-hosted

You are encouraged to run your own acme-dns instance, because you are effectively authorizing the acme-dns server to act on your behalf in providing the answer to the challenging CA, making the instance able to request (and get issued) a TLS certificate for the domain that has CNAME pointing to it.

See the INSTALL section for information on how to do this.

Installation

  1. Install Go 1.13 or newer.

  2. Build acme-dns:

git clone https://github.com/joohoi/acme-dns
cd acme-dns
export GOPATH=/tmp/acme-dns
go build
  1. Move the built acme-dns binary to a directory in your $PATH, for example: sudo mv acme-dns /usr/local/bin

  2. Edit config.cfg to suit your needs (see configuration). acme-dns will read the configuration file from /etc/acme-dns/config.cfg or ./config.cfg, or a location specified with the -c flag.

  3. If your system has systemd, you can optionally install acme-dns as a service so that it will start on boot and be tracked by systemd. This also allows us to add the CAP_NET_BIND_SERVICE capability so that acme-dns can be run by a user other than root.

    1. Make sure that you have moved the configuration file to /etc/acme-dns/config.cfg so that acme-dns can access it globally.

    2. Move the acme-dns executable from ~/go/bin/acme-dns to /usr/local/bin/acme-dns (Any location will work, just be sure to change acme-dns.service to match).

    3. Create a minimal acme-dns user: sudo adduser --system --gecos "acme-dns Service" --disabled-password --group --home /var/lib/acme-dns acme-dns.

    4. Move the systemd service unit from acme-dns.service to /etc/systemd/system/acme-dns.service.

    5. Reload systemd units: sudo systemctl daemon-reload.

    6. Enable acme-dns on boot: sudo systemctl enable acme-dns.service.

    7. Run acme-dns: sudo systemctl start acme-dns.service.

  4. If you did not install the systemd service, run acme-dns. Please note that acme-dns needs to open a privileged port (53, domain), so it needs to be run with elevated privileges.

Using Docker

  1. Pull the latest acme-dns Docker image: docker pull joohoi/acme-dns.

  2. Create directories: config for the configuration file, and data for the sqlite3 database.

  3. Copy configuration template to config/config.cfg.

  4. Modify the config.cfg to suit your needs.

  5. Run Docker, this example expects that you have port = "80" in your config.cfg:

docker run --rm --name acmedns                 \
 -p 53:53                                      \
 -p 53:53/udp                                  \
 -p 80:80                                      \
 -v /path/to/your/config:/etc/acme-dns:ro      \
 -v /path/to/your/data:/var/lib/acme-dns       \
 -d joohoi/acme-dns

Docker Compose

  1. Create directories: config for the configuration file, and data for the sqlite3 database.

  2. Copy configuration template to config/config.cfg.

  3. Copy docker-compose.yml from the project, or create your own.

  4. Edit the config/config.cfg and docker-compose.yml to suit your needs, and run docker-compose up -d.

DNS Records

Note: In this documentation:

  • auth.example.org is the hostname of the acme-dns server
  • acme-dns will serve *.auth.example.org records
  • 198.51.100.1 is the public IP address of the system running acme-dns

These values should be changed based on your environment.

You will need to add some DNS records on your domain's regular DNS server:

  • NS record for auth.example.org pointing to auth.example.org (this means, that auth.example.org is responsible for any *.auth.example.org records)
  • A record for auth.example.org pointing to 198.51.100.1
  • If using IPv6, an AAAA record pointing to the IPv6 address.
  • Each domain you will be authenticating will need a _acme-challenge CNAME subdomain added. The client you use will explain how to do this.

Testing It Out

You may want to test that acme-dns is working before using it for real queries.

  1. Confirm that DNS lookups for the acme-dns subdomain works as expected: dig auth.example.org.

  2. Call the /register API endpoint to register a test domain:

$ curl -X POST https://auth.example.org/register
{"username":"eabcdb41-d89f-4580-826f-3e62e9755ef2","password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0","fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org","subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf","allowfrom":[]}
  1. Call the /update API endpoint to set a test TXT record. Pass the username, password and subdomain received from the register call performed above:
$ curl -X POST \
  -H "X-Api-User: eabcdb41-d89f-4580-826f-3e62e9755ef2" \
  -H "X-Api-Key: pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0" \
  -d '{"subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", "txt": "___validation_token_received_from_the_ca___"}' \
  https://auth.example.org/update

Note: The txt field must be exactly 43 characters long, otherwise acme-dns will reject it

  1. Perform a DNS lookup to the test subdomain to confirm the updated TXT record is being served:
$ dig -t txt @auth.example.org d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org

Configuration

[general]
# DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53
# In this case acme-dns will error out and you will need to define the listening interface
# for example: listen = "127.0.0.1:53"
listen = "127.0.0.1:53"
# protocol, "both", "both4", "both6", "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "both"
# domain name to serve the requests off of
domain = "auth.example.org"
# zone name server
nsname = "auth.example.org"
# admin email address, where @ is substituted with .
nsadmin = "admin.example.org"
# predefined records served in addition to the TXT
records = [
    # domain pointing to the public IP of your acme-dns server 
    "auth.example.org. A 198.51.100.1",
    # specify that auth.example.org will resolve any *.auth.example.org records
    "auth.example.org. NS auth.example.org.",
]
# debug messages from CORS etc
debug = false

[database]
# Database engine to use, sqlite3 or postgres
engine = "sqlite3"
# Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres
# Please note that the default Docker image uses path /var/lib/acme-dns/acme-dns.db for sqlite3
connection = "/var/lib/acme-dns/acme-dns.db"
# connection = "postgres://user:password@localhost/acmedns_db"

[api]
# listen ip eg. 127.0.0.1
ip = "0.0.0.0"
# disable registration endpoint
disable_registration = false
# listen port, eg. 443 for default HTTPS
port = "443"
# possible values: "letsencrypt", "letsencryptstaging", "cert", "none"
tls = "letsencryptstaging"
# only used if tls = "cert"
tls_cert_privkey = "/etc/tls/example.org/privkey.pem"
tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem"
# only used if tls = "letsencrypt"
acme_cache_dir = "api-certs"
# optional e-mail address to which Let's Encrypt will send expiration notices for the API's cert
notification_email = ""
# CORS AllowOrigins, wildcards can be used
corsorigins = [
    "*"
]
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = "X-Forwarded-For"

[logconfig]
# logging level: "error", "warning", "info" or "debug"
loglevel = "debug"
# possible values: stdout, TODO file & integrations
logtype = "stdout"
# file path for logfile TODO
# logfile = "./acme-dns.log"
# format, either "json" or "text"
logformat = "text"

HTTPS API

The RESTful acme-dns API can be exposed over HTTPS in two ways:

  1. Using tls = "letsencrypt" and letting acme-dns issue its own certificate automatically with Let's Encrypt.
  2. Using tls = "cert" and providing your own HTTPS certificate chain and private key with tls_cert_fullchain and tls_cert_privkey.

Where possible the first option is recommended. This is the easiest and safest way to have acme-dns expose its API over HTTPS.

Warning: If you choose to use tls = "cert" you must take care that the certificate does not expire! If it does and the ACME client you use to issue the certificate depends on the ACME DNS API to update TXT records you will be stuck in a position where the API certificate has expired but it can't be renewed because the ACME client will refuse to connect to the ACME DNS API it needs to use for the renewal.

Clients

Authentication hooks

Libraries

Changelog

  • v0.8
    • NOTE: configuration option: "api_domain" deprecated!
    • New
      • Automatic HTTP API certificate provisioning using DNS challenges making acme-dns able to acquire certificates even with HTTP api not being accessible from public internet.
      • Configuration value for "tls": "letsencryptstaging". Setting it will help you to debug possible issues with HTTP API certificate acquiring process. This is the new default value.
    • Changed
      • Fixed: EDNS0 support
      • Migrated from autocert to certmagic for HTTP API certificate handling
  • v0.7.2
    • Changed
      • Fixed: Regression error of not being able to answer to incoming random-case requests.
      • Fixed: SOA record added to a correct header field in NXDOMAIN responses.
  • v0.7.1
    • Changed
      • Fixed: SOA record correctly added to the TCP DNS server when using both, UDP and TCP servers.
  • v0.7
    • New
      • Added an endpoint to perform health checks
    • Changed
      • A new protocol selection for DNS server "both", that binds both - UDP and TCP ports.
      • Refactored DNS server internals.
      • Handle some aspects of DNS spec better.
  • v0.6
    • New
      • Command line flag -c to specify location of config file.
      • Proper refusal of dynamic update requests.
      • Release signing
    • Changed
      • Better error messages for goroutines
  • v0.5
    • New
      • Configurable certificate cache directory
    • Changed
      • Process wide umask to ensure created files are only readable by the user running acme-dns
      • Replaced package that handles UUIDs because of a flaw in the original package
      • Updated dependencies
      • Better error messages
  • v0.4 Clear error messages for bad TXT record content, proper handling of static CNAME records, fixed IP address parsing from the request, added option to disable registration endpoint in the configuration.
  • v0.3.2 Dockerfile was fixed for users using autocert feature
  • v0.3.1 Added goreleaser for distributing binary builds of the releases
  • v0.3 Changed autocert to use HTTP-01 challenges, as TLS-SNI is disabled by Let's Encrypt
  • v0.2 Now powered by httprouter, support wildcard certificates, Docker images
  • v0.1 Initial release

TODO

  • Logging to a file
  • DNSSEC
  • Want to see something implemented, make a feature request!

Contributing

acme-dns is open for contributions. If you have an idea for improvement, please open an new issue or feel free to write a PR!

License

acme-dns is released under the MIT License.

acme-dns's People

Contributors

ajedi32 avatar blkeller avatar cpu avatar cure avatar daniel15 avatar digitalbrains1 avatar gabe565 avatar golint-fixer avatar joohoi avatar jsoref avatar julienschmidt avatar jvanasco avatar koesie10 avatar kugelschieber avatar lateagain avatar leonkyneur avatar rmbolger avatar sjtoik avatar stblassitude avatar webprofusion-chrisc avatar woutertinus avatar yannik avatar zeevallin avatar znerol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

acme-dns's Issues

Document Feature #28 (multiple TXT) in README

I see 2 TXT records saved when I update the records:

4ebb3343-e8f7-454f-aea6-86994592eb8d.auth.my.tld. 0 IN TXT "BqLhqbbdhb7wkdG45c86ZtnMW7PesxsJsIdHMG0WGMg"
4ebb3343-e8f7-454f-aea6-86994592eb8d.auth.my.tld. 0 IN TXT "M3HUkG12CG5VN2uE2DKgVaYI5rYpBqb7rSy8JNhCVsk"

Thought it was a bug until I saw #28. Could we add documentation for this feature in the README that it isn't a bug? TYVM!

Add two TXT records for the same subdomain

Hello,

Due the upcoming Let's Encrypt wildcard certificates, if we want a certificate covering domain.tld and *.domain.tld we need to create two _acme-challenge.domain.tld TXT records, one for the token of domain.tld and another one for *.domain.tld

_acme-challenge.domain.tld TXT tokenfordomain
_acme-challenge.domain.tld TXT tokenforwildcard

Using acme-dns we only need to create an _acme-challenge.domain.tld CNAME record pointing to our fulldomain aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.auth.whatever.tld but as far as I know, using acme-dns api we can only set 1 TXT record for our subdomain so it shouldn't be possible to issue a cert covering domain.tld and *.domain.tld.

Is there an option/workaround to accomplish this task?. If not, it should be great to add the possibility to add an additional TXT record for a subdomain.

Thank you very much for your work.

Cheers,
sahsanu

Handling registration requests with broken/incomplete JSON payload

To continue from discussion started here:
#20 (comment)

ping @koesie10

Which approach would be the intuitive and appropriate if broken or incomplete JSON object (or whatever payload) is sent to registration endpoint:

  1. acme-dns should return error, like 400 Bad Request with descriptive error message.
  2. Registration should complete with default values, and return these to the user.

Previously I felt that the second one would be the correct approach, but now I'm starting to lean towards the first one.

Possible missing DNS settings for the server (Auth Failures)

I am still having some failed authorizations and feel like I am missing something , here are some details

Config

[general]
# dns interface
listen = ":53"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp"
# domain name to serve the requests off of 
domain = "dns.codepier.io"
# zone name server 
nsname = "ns1.codepier.io"
# admin email address, where @ is substituted with .
nsadmin = "codepier.io"
# predefined records served in addition to the TXT
records = [
    # default A
    "dns.codepier.io. A 45.77.100.41",
    # A 
    "ns1.dns.codepier.io. A 45.77.100.41",
    # NS
    "dns.codepier.io. NS ns1.dns.codepier.io.",
]

DNS Records

A :dns.codepier.io
NS : ns1.dns.codepier.io

Is there anything else that i need to setup?

More Info

I've setup a script to automatically get the token and update it via the API , and that seems to be working

Output from 8-set-token.sh:
{"txt": "1HQjYS6NlSne1RCeCxfTisFAwr8-9fEbGEQ4jWtzBnQ"}

But the actual authorization fails

Failed authorization procedure. evanpolicinski.com (dns-01): urn:ietf:params:acme:error:connection :: The server could not connect to the client to verify the domain :: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.evanpolicinski.com

K8S: Create wildcard SSL using acmedns provider

I have tried to create wildcard SSL certificate using k8s certmanager and issuer. I have created the credentials by POST requesting to /register URL and tested the acmedns successfully. However I am unable to create new wildcard SSL certificate using the k8s issuer. I am adding my issuer YAML file below

apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
  annotations:
  name: letsencrypt-wildcard-prod
  namespace: default
spec:
  acme:
    dns01:
      providers:
        acmedns:
          accountSecretRef:
            key: acmedns.json
            name: acme-dns
          host: http://auth.mydomain.com
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod
    server: https://acme-v02.api.letsencrypt.org/directory

I have created the secret acme-dns using the json output got from the /register output.
Also, adding the k8s certificate YAML here

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: wildcard-mydomain.com
  namespace: default
spec:
  acme:
    config:
    - dns01:
        provider: acmedns
      domains:
      - '*.mydomain.com'
  commonName: '*.mydomain.com'
  dnsNames:
  - '*.mydomain.com'
  issuerRef:
    kind: Issuer
    name: letsencrypt-wildcard-prod
  secretName: wildcard-mydomain.com-tls

I am getting the following error from the cert-manager:
###############################################################################
E1129 16:30:31.881025 1 reflector.go:205] github.com/jetstack/cert-manager/pkg/client/informers/externalversions/factory.go:71: Failed to list *v1alpha1.Issuer: v1alpha1.IssuerList: Items: []v1alpha1.Issuer: v1alpha1.Issuer: Spec: v1alpha1.IssuerSpec: IssuerConfig: ACME: v1alpha1.ACMEIssuer: DNS01: v1alpha1.ACMEIssuerDNS01Config: Providers: []v1alpha1.ACMEIssuerDNS01Provider: ReadArrayCB: expect [ or n, but found {, error found in #10 byte of ...|oviders":{"acmedns":|..., bigger context ...|81551da95"},"spec":{"acme":{"dns01":{"providers":{"acmedns":{"accountSecretRef":{"key":"acmedns.json|...
E1129 16:30:32.887374 1 reflector.go:205] github.com/jetstack/cert-manager/pkg/client/informers/externalversions/factory.go:71: Failed to list *v1alpha1.Issuer: v1alpha1.IssuerList: Items: []v1alpha1.Issuer: v1alpha1.Issuer: Spec: v1alpha1.IssuerSpec: IssuerConfig: ACME: v1alpha1.ACMEIssuer: DNS01: v1alpha1.ACMEIssuerDNS01Config: Providers: []v1alpha1.ACMEIssuerDNS01Provider: ReadArrayCB: expect [ or n, but found {, error found in #10 byte of ...|oviders":{"acmedns":|..., bigger context ...|81551da95"},"spec":{"acme":{"dns01":{"providers":{"acmedns":{"accountSecretRef":{"key":"acmedns.json|...
########################################################################

Client implementations

Acme-dns needs client implementations for clients in order to be useful. If you have written a client implementation, please let me know, and I'll add a link to it to the README.md for people to find and use.

Currently the only publicly available client implementation that I know of is a Certbot authentication hook that can be found at: https://github.com/joohoi/acme-dns-certbot . This can be used as a reference for ways to handle the credential storage and communication towards acme-dns instance.

sync README with config, perhaps better docs about ips

The readme and config differ a bit

For example, the README shows

# listen ip, default "" listens on all interfaces/addresses
ip = "127.0.0.1"

But the config was changed to be:

listen ip eg. 127.0.0.1

ip = "0.0.0.0"

i'm sure multiple default elements were changed - that's the only one I noticed from my limited knowledge of the library. the content of the README installation guide should reflect the current config options/defaults and docstrings.

In terms of docs about ips... defaulting to 127.0.0.1 is probably safest, as as it will only work from the same machine (or require a proxy on the machine) since there are not access controls in place- but many people will likely want to use 0.0.0.0 for testing. was there a reason this was changed to 0.0.0.0?

https://raw.githubusercontent.com/joohoi/acme-dns/3343d943d604be8bc0ab0830bccfb6459d1ae95c/README.md#L230-L240

https://github.com/joohoi/acme-dns/blob/master/config.cfg#L34-L39

CNAME records are returned as NXDOMAIN

I'm using <uid>.acme-dns.mydomain.org for the letsencrypt TXT records and would like to use acme-dns.mydomain.org as API endpoint.

These are the records I've configured in config.cfg:

records = [ 
    "acme-dns.mydomain.org. CNAME hostname.mydomain.org",
    "acme-dns.mydomain.org. NS hostname.mydomain.org",
]

In the mydomain.org zone I have configured acme-dns.mydomain.org NS hostname.mydomain.org accordingly.

Unfortunately, querying acme-dns.mydomain.org results in NXDOMAIN:
time="2018-03-02T11:45:27Z" level=debug msg="Answering question for domain" domain=acme-dns.mydomain.org. qtype=A rcode=NXDOMAIN

Is it possible to get CNAMEs to resolve correctly? I'd really prefer this over hardcoding an A-record.

Status 401 (Unauthorized) when trying to post update request

I am trying to update a DNS record using Java.
I always get an 401 status response, but I cannot see anything wrong in my code.

` private void contactAcmeDnsServer(String digest) {
Console.out("Digest: " + digest);

	try {
		Content content = Request.Post("http://localhost:9443/register").execute().returnContent();

		JSONParser parser = new JSONParser();
		JSONObject json = (JSONObject) parser.parse(content.asString());

		String fulldomain = (String) json.get("fulldomain");
		String subdomain = (String) json.get("subdomain");
		String username = (String) json.get("username");
		String password = (String) json.get("password");
		
		String url = "http://localhost:9443/update";

		@SuppressWarnings("resource")
		HttpClient client = new DefaultHttpClient();
		HttpPost request = new HttpPost(url);
		request.addHeader("X-Api-Key", password);
		request.addHeader("X-Api-User", username);

		String JSON_STRING = "{\r\n" + "\"subdomain\": \"_acme-challenge." + subdomain + "\",\r\n" + "\"txt\": \""
				+ digest + "\"\r\n" + "}";

		StringEntity stringEntity = new StringEntity(JSON_STRING, "UTF-8");

		request.setEntity(stringEntity);

		HttpResponse response = client.execute(request);

		Console.out("rc: " + String.valueOf(response.getStatusLine().getStatusCode()));

		BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

		StringBuffer result = new StringBuffer();
		String line = "";
		while ((line = rd.readLine()) != null) {
			result.append(line);
		}

		Console.out(result.toString());

	} catch (IOException e) {
		e.printStackTrace();
	} catch (ParseException e) {
		e.printStackTrace();
	}`

Aside from this, am I supposed to do the POST /register every time before the POST /update?
Is the "_acme-challenge." part automatically added in the POST /update or is my JSON_STRING object correct?

How do I add the CNAME record to the server?

I can see in the video how to get the CNAME using dig, but how do I actually ADD the record? I think, this is missing in the video.

Sorry for asking such a dumb question. I am a developer :)

Bad Subdomain

Im getting a bad subdomain currently , Ill take down this instance once i get it resolved but :

## Acme-DNS Register
curl -X "POST" "http://159.65.179.47/register"
## Acme-DNS Update
curl -X "POST" "http://159.65.179.47/update" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -H 'X-Api-Key: n8o2nd7E4JtqWlTEbyEZAjs5mZr1wc8Ehju2SNhG' \
     -H 'X-Api-User: 105f0386-6d4b-41ca-9c17-d5a0481e7e36' \
     -d $'{
  "txt": "test",
  "subdomain": "cc3ddadd-0c05-4e5e-8d3f-2d2b72e9c083"
}'

Response :

{ error : 'bad_subdomain'}

Is there something im doing wrong?

systemd socket activation

systemd provides socket activation as an option for listening services, and is particularly handy for services that listen on privileged ports. It would be useful to be able to operate a very infrequently-accessed service like this via on-demand activation.

It looks like miekg/dns already supports socket activation via ActivateAndServe (analogous to ListenAndServe), and coreos/go-systemd provides a handy activation abstraction.

(And because you can configure multiple listeners, it would be easy to also grab the listener for HTTP from systemd as well.)

Mention in README that IPv6-only is OK?

I was considering adding this to add this to the README, but I'm not quite sure where it would fit.

Let's Encrypt fully supports IPv6-only servers, both for http-01 challenges as well as dns-01 challenges. Commonly, IPv4 addresses are very limited in number while IPv6 addresses are abundant. As an example, I've got a VPS that just has a single IPv4 address and an entire /64 IPv6 range (2^64 addresses)!

This can lead to cases where someone might only have IPv6 addresses available for use. A common example is where a server only has one IPv4 address that's already being used by some other DNS service. In cases like this, binding acme-dns just to an IPv6 address is totally fine.

My configuration looks like:

[general]
# dns interface
listen = "[2605:6400:20:92e::eff]:53"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp6"
...

[api]
...
# listen ip, default "" listens on all interfaces/addresses
ip = "[2605:6400:20:92e::eff]"

Note that ip under api needs to be in square brackets when listening to an IPv6 address.

SQLite database is world-readable by default

The acme-dns database contains sensitive information such as API keys, and should not be readable by other Linux users by default. However, when I run acme-dns it creates the acme-dns.db file with its permission bits set to 644.

Restrict registration

Given that this DNS server is going to be used to authorise requests for SSL infrastructure, it would be nice to be able to restrict registration requests being sent to the server.

The ability to set a password in the configuration, then have this checked when registration requests are made to the server then deny them if incorrect/not provided would be pretty great.

If it would be possible to add an option to disable registration, it'd be appreciated.

Thanks in advance for your consideration.

raise an error if acme-dns can't listen/bind to a specified port.

while doing some debugging of firewall rules, I realized that acme-dns will start up fine as an unprivileged user who can't bind to port 53 (if that is the configured port). I think it should probably raise an error OR print a warning line in these situations.

acme-dns needs directory permissions in systemd (documentation)

I updated my install to control acme-dns via systemd, with an acme-dns user.

I changed the ownership of items in /etc/acme-dns to acme-dns.

If the /etc/acme-dns directory is owned by root, there are errors in accessing the existing database /etc/acme-dns/acme-dns.db. If the directory is owned by acme-dns, the db is read fine.

Stated differently,

# this results in db errors
chown acme-dns:acme-dns /etc/acme-dns/* 

# this works
chown -R acme-dns:acme-dns /etc/acme-dns

I'm not sure how/why this is happing, but I think it is due to sqlite not being able to make lock files.

letsencrypt HTTP-01 challenge possible?

Just noticed my certificate cannot renew, I think it's only since LetsEncrypt dropped support for TLS-SNI.

01/02/2018 12:14:43 pmINFO[0029] http: TLS handshake error from 10.42.176.26:51538: acme/autocert: unable to authorize "xxxxxxx.net"; tried ["tls-sni-02" "tls-sni-01"]

is there any way I can continue to use autocert for the API but with HTTP-01 challenge?

How to add other types of records through the API?

The documentations shows how to add TXT records(assuming that you defer the verification from your main DNS server using a CNAME) but I wonder if it supports other record types and if it can be used as the main DNS server(i.e. point the NS records to it). Using a different server just for ACME verifications doesn't seem right.

registration with invalid `allowfrom` fails open

I didn't read the documentation very carefully and thought I could POST /registration with an allowfrom entry for a single host address instead of a CIDR range, e.g.:

{
    "allowfrom": [
        "127.0.0.1"
    ]
}

acme-dns filters this invalid allowfrom in acmedb.Register using cidrslice.ValidEntries. The registration is created successfully with no errors, but the returned allowfrom is empty and so is the field in the database for this user:

{
  "username": "<rand>",
  "password": "<rand>",
  "fulldomain": "<rand>.example.com",
  "subdomain": "<rand>",
  "allowfrom": []
}

The documentation specifically says to use a CIDR range but I think there might be a case to be made for rejecting the registration with an error when the allowfrom contains invalid entries. Since allowfrom is a security control I think acme-dns should be conservative and fail fast when it can't fulfill the request as received.

@joohoi What do you think?

PostgreSQL go driver

I set up acme-dns as a service with an unprivileged acme-dns user. I edited the config file to use PostgreSQL and downloaded the pq driver for go. However, being new to go, I am at a lost about where to place the pq package. The command "go get githup/com/lib/pq" installs the river in ~/go by default but since acme-dns is shell-less, where does the package should be installed. The recommendation is to set the $GOPATH variable but I believe that this would not work for acme-dns.

The messages log file (on CENTOS) shows:
acme-dns: time="2018-06-27T22:36:18-04:00" level=error msg="Could not open database [sql: unknown driver "" (forgotten import?)]"

For go neophytes, an addition to the documentation would be greatly appreciated.

Create certificates for multiple hosts without adding a CNAME record for each?

First off, thanks for building acme-dns - it's just what I was looking for! I'm trying to do the following: I want to generate certificates for the servers foo.example.org, bar.example.org etc. This works fine. However I have to manually add the following in my providers DNS config:

_acme-challenge CNAME 3882beda-a862-4de8-b3ab-4f528fe88845.auth.example.org
_acme-challenge.foo CNAME _acme-challenge
_acme-challenge.bar CNAME _acme-challenge

As the hosts (foo, bar etc.) are being created rather dynamic I would prefer to prevent creating them by hand if possible (also I would prefer to keep the names of the hosts out of my providers DNS. Wildcards obviously won't work and I don't want to generate a wildcard certificate on each hosts as that seems more of a workaround than a real solution.

The only solution so far that I could think of would be to use my providers API to somehow dynamically create the _acme-challenge.foo record. I would have need to have the client (e.g. lego) to do this before requesting the _acme-challenge record. But unfortunately this implies a 'race' condition because to do that I would put my API keys on each host (which is exactly what I'm trying to prevent with acme-dns).

Would there be any (other) ideas to do this smarter / more dynamic?

config location

I'd like to run two instances with different configs. can a command line flag be added to specify which config to launch with?

Restructure README installation & configuration instructions.

Readme should get restructured:

  • The DNS configuration and Testing it out - sections should follow the Install section.
  • Troubleshooting section should get added after Testing it out - section.
  • Different sections should be linked to from the top of the README for easier access.

Run Dockerized server process as unprivileged user by default

Would be nice from a security perspective if the Dockerized version of acme-dns automatically started as an unprivileged user (instead of as root).

The container does provide some level of isolation from the host system already (though as I understand it that isolation isn't perfect, especially against root), but no sense in giving the public-facing server any more permissions than necessary.

No JSON object could be decoded

I have put acme-dns into a Docker container with exposed ports 53 and 443. I get response for requests on 53/UDP but I get no response on 443/TCP. All I get is "No JSON object could be decoded"

I have the following (DEFAULT) settings in the api section of config.cfg

 [api]
 # domain name to listen requests for, mandatory if using tls = "letsencrypt"
 api_domain = ""
 # listen port, eg. 443 for default HTTPS
 port = "443"
 # possible values: "letsencrypt", "cert", "none"
 tls = "none"

Any hint, where to look into?

Arm docker image

Could you add a arm docker build and tag. I'm attempting to run on raspi 3b+
latest tag will crash immediately due to arm proc

Raise the cap for TXT records per subdomain

I recently released a new PowerShell based ACMEv2 client, Posh-ACME. It supports a DNS challenge plugin system and I was all ready to write a plugin for acme-dns after reading a bit about it. But after digging deeper into how it works, I realized that its usage model isn't really compatible with how my client's DNS challenge processing works.

My client currently tries to publish all required TXT records for a given certificate before sending the validation requests to the ACME server. And if I understand how the 2 record cap in acme-dns works, it means that a SAN cert with more than two names will never be properly validated. As soon as the 3rd record gets added, the oldest one is overwritten and its validation will ultimately fail.

Let's Encrypt's current rate limits enforce a maximum of 100 names per certificate. So raising the acme-dns cap to 100 before rolling over seems like the simplest way to successfully validate a single cert with the maximum 100 names.

Also, since acme-dns seems to be more of a fire-and-forget publishing model rather than add-and-remove, it might also make sense to add what is basically a TTL on the records that get published. Not an actual DNS TTL, but like a database TTL. When the records "expire", they're automatically removed.

I haven't had a chance to look at acme-dns's code yet. So I have no idea what a suggestion like this might take to implement. But does it seem doable?

Problem start acme-dns in installation

After install and config. exec acme-dns
this error not start the service.
INFO[0000] Using config file file=/etc/acme-dns/config.cfg
ERRO[0000] Could not open database [sql: unknown driver "" (forgotten import?)]

Make DNS server listen on one IPv6 address

I want to run acme-dns and have it listen on just a single IPv6 address, since I already have another DNS server listening on some other IPs on the same server. I've edited the configuration like this:

[general]
# dns interface
listen = "[2605:6400:20:92e::eff]:53"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp6"

...

[api]
# listen ip, default "" listens on all interfaces/addresses
#ip = "127.0.0.1"
ip = ""
# listen port, eg. 443 for default HTTPS
port = "8496"

However, when I run it, it seems like it's only listening on the API port and not on the DNS one:

13:28 daniel@vps03 /home/daniel
% sudo netstat -tulnp | grep acme-dns
tcp6       0      0 :::8496                 :::*                    LISTEN      28247/acme-dns

Am I using the correct syntax? It's a bit confusing because the API section has separate "ip" and "port" options, but I just see a single "listen" option for the DNS server, so I just tried to guess the syntax.

What is the purpose of having both username and password?

In the current acme-dns design, each subdomain registration generates a random username and password to authenticate with when updating records. But why have both a username and password when a single password or api token value would do?

In most authentication setups, you have both because the username can be exposed on user facing UI and is considered more or less public info. But in this architecture, the username is kept secret for the duration of its lifetime. So it's really just like you have two passwords that have to be used together. So why not combine them into one value and reduce the complexity a bit?

Allow more than two records?

In my scenario acme-dns is hosted on the same machine as the http server that requests certificate, so it can renew certificates automatically forever (with acme credentials stored on local disk).

However, whenever the whole server is migrated to another machine, subdomain changes unless I migrate the local auth data that those two services established between each other on old machine.

Since subdomain changes, I need to manually update _acme-challenge to newly generated subdomain so that acme-dns on new machine can receive DNS requests. Since I don't want to update many records, I reuse the same acme-dns subdomain for multuple domains in a certificate. I.e. I point _acme-challenge of multiple domains (i.e. foo.example.com, bar.example.com, example.com, *.example.com) to a single acme-challenge.example.com, and then acme-challenge.example.com to long-acme-generated-domain.acme-dns.example.com. In that case when a machine migrated and generated subdomain changed, I only need to update one mapping (from acme-challenge.example.com to long-acme-generated-domain.acme-dns.example.com, but not each individual subdomain that the certficates are generated for).

However, it appears in that case ACME challenge does not succeed reliably, as new records override old ones, and when I want to verify say 5 domains, only two get valid records.

So I guess several things could be done to make it better:

  • Support rolling update of 10 records rather than 2. This seems like a one-line change, but I wonder if the certificate issuers will be confused by seeing 10 records.
  • Add local mode that does not have authentication, and instead lets client choose subdomains and records for them. In that case I could set up mapping from my domains to acme-dns subdomains once, and it would remain the same whatever migration between machines happen.
  • I could store auth data and database of acme-dns when moving between machines. This sounds strange, as it really feels that such data doesn't need to be persistently stored. It is also inconvenient if apart from those credentials, the server is stateless.
  • For multi-domain certificate do updates and verification one-by-one, rather than current behaviour (of acme.sh) to update all txt records, and then verify all domains.
  • Even without local mode (even though keeping track of login/password in local mode feels silly), how about we allow client to choose subdomain name? That way clients could have some deterministic mapping from their domain names to acme-dns domain names (i.e. hash with salt) and register accordingly. However when acme-dns is deployed locally, it would get advantage that if it gets redeployed (i.e. due to the whole stack being migrated to another machine), even though registration records would get lost, client process could re-do registration, and preserve the same acme-dns subdomains, hence no need to manually update CNAME records.

I guess in my case (again, local mode), I'd be happy with even simpler case:

  • A tiny DNS server
  • That can receive unauthenticated instructions like "for domain foo.bar.example.com" set TXT records to ['abc', 'xyz'].
  • ...and then keep track of those mappings and respond to DNS requests accordinlgy.

It seems in my case the complexity of acme-dns (which was designed to be deployed publicly and used by many clients) gets in the way.

Port Binding Errors

Does this currently give any sort of visible error or warning if it can't bind the DNS port? I'll admit straight out that this largely is pebkac/rtfm error on my part, but I spent longer than I'd care to admit today troubleshooting today because systemd-resolved was hogging the port on Ubuntu server, putting this in a state where everything looked green everywhere I checked, no more config errors, register and update work fine, but nothing will resolve for obvious(in retrospect) reasons. It would be nice to have something a bit more in your face, like when you have config errors and it straight up spits out an error and refuses to start.

dynamic dns

The acme-dns server seems to be responding to dynamic-dns requests in a way that makes it looks like it has accepted an update request. It doesn't seem to actually do so, but it confuses some security scanners into stating falsely that the server is honouring dynamic-dns requests. In the case of a dynamic-dns request, acme-dns should respond with RCODE REFUSED(5)

$ nsupdate
> server xxx.xxx.xxx.xxx 53
> update add foo.letsencrypt.example.com 86400 A 127.0.0.1
>  send
> quit
$ dig @xxx.xxx.xxx.xxx foo.letsencrypt.example.com

; <<>> DiG 9.4.2-P2 <<>> @xxx.xxx.xxx.xxx foo.letsencrypt.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id:
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;foo.letsencrypt.example.com.      IN      A

;; Query time: 77 msec
;; SERVER: xxx.xxx.xxx.xxx#53(xxx.xxx.xxx.xxx)
;; WHEN: Tue Sep 18 18:40:01 2018
;; MSG SIZE  rcvd: 42

TCP or UDP? Why not both?

acme-dns has a config option that lets you choose whether to serve DNS requests over UDP or over TCP.

However, RFC7766 requires that:

All general-purpose DNS implementations MUST support both UDP and TCP transport.

And unfortunately, "both" doesn't seem to be an option.

Registration with allowfrom set does not work?

Unless I'm doing something really stupid here, I don't seem to be able to be able to update domains when allowfrom has been set during registration.

I use ip addr to retrieve all the CIDR ranges assigned to my server, then register as follows:

root@london ~ curl -s -X POST http://auth.mydnsdomain.co.uk:8080/register \
--data '{"allowfrom": ["127.0.0.1/8", "::1/128", "139.XXX.XXX.153/24", "192.XXX.XXX.197/17", "2a01:XXXX::XXXX:XXXX:XXXX:923d/64", "fe80::XXXX:XXXX:XXXX:923d/64"]}' | python -m json.tool
{
    "allowfrom": [
        "127.0.0.1/8",
        "::1/128",
        "139.XXX.XXX.153/24",
        "192.XXX.XXX.197/17",
        "2a01:XXXX::XXXX:XXXX:XXXX:923d/64",
        "fe80::XXXX:XXXX:XXXX:923d/64"
    ],
    "fulldomain": "1937e870-d239-4cb2-99b2-d1979c2608e3.auth.mydnsdomain.co.uk",
    "password": "54mt4yLIRJZIXiK161jZghvuIxFz09Tj1sZY_vpZ",
    "subdomain": "1937d870-d239-4cb2-99b2-d1979q2608a3",
    "username": "p0624f07-b4a7-4d61-8d28-4f7e63621952"
}

Note: IP addresses have been partially replaced with XXX/XXXX, the real values however are used for the commands.

I then attempt to update that sub-domain:

root@london ~ curl -s -X POST http://auth.mydnsdomain.co.uk:8080/update \
-H "X-Api-User: p0624f07-b4a7-4d61-8d28-4f7e63621952" \
-H "X-Api-Key: 54mt4yLIRJZIXiK161jZghvuIxFz09Tj1sZY_vpZ" \
--data '{"subdomain": "1937d870-d239-4cb2-99b2-d1979q2608a3", "txt": "___validation_token_recieved_from_the_ca___"}' | python -m json.tool
{
    "error": "forbidden"
}

However the update fails. acme-dns displays the following:

DEBU[1269] Created new user                              user=p0624f07-b4a7-4d61-8d28-4f7e63621952
ERRO[1542] Update not allowed from IP                    error=ip_unauthorized

If I register without seting allowfrom, everything works fine:

curl -s -X POST http://auth.mydnsdomain.co.uk:8080/register | python -m json.tool
{
    "allowfrom": [],
    "fulldomain": "ad53ddb7-38ad-42fd-9d65-854769764df0.auth.mydnsdomain.co.uk",
    "password": "rqSI-YHVf12jfw-FgkQzJrhyUJ_rU9E4_37WP_6h",
    "subdomain": "ab53ddb7-38ad-42fd-9d65-854769764da0",
    "username": "945b3pf0-cc24-4008-9c99-270b13534d4q"
}

curl -s -X POST http://auth.mydnsdomain.co.uk:8080/update \
-H "X-Api-User: 945b3pf0-cc24-4008-9c99-270b13534d4q" \
-H "X-Api-Key: rqSI-YHVf12jfw-FgkQzJrhyUJ_rU9E4_37WP_6h" \
--data '{"subdomain": "ab53ddb7-38ad-42fd-9d65-854769764da0", "txt": "___validation_token_recieved_from_the_ca___"}' | python -m json.tool
{
    "txt": "___validation_token_recieved_from_the_ca___"
}

Am I missing something here, or is it broken?

How do I make the subdomain.auth.domain.tld available from the outside

Hi I created a docker container for acme-dns, I registered there and I can update the token via post request. I am using the jwilder nginx proxy.
I installed a CNAME entry for _acme-challenge.jotunheim.de to subdomain.auth.jotunheim.de
This entry points to my IP adress.
What I don't understand is, what I need to do now, to make the TXT entry available from the outside. Do I need to portforward port 53 to the inside acme-dns, right? What else?

Clarification of a Few Points

I'm using acme-dns for my wildcard domain and I was hoping to get a few points clarified for using it

  1. In my config I added the following extra records (changed here for anonymity):
records = [
    # default A
    "auth.example.org. A 192.168.1.100",
    # A
    "ns.auth.example.org. A 192.168.1.100",
    # NS
    "auth.example.org. NS ns.auth.example.org.",
]

Are all these records necessary? Following the readme, I also added the same NS record and A record for ns.auth to my primary DNS, so at least the bottom two seem potentially redundant to me but I may be misunderstanding something about how DNS resolves the names.

  1. Since I use this in a home setup, I am at my ISPs mercy for an IP address. For my primary DNS provider I use dynamic dns records to keep the records updated. Is there any advice for updating acme-dns configuration automatically when the external IPs change?

  2. I'm going to try to add support for acme-dns to cert-manager as part of a larger system, and I won't be able to forward port 53 during development to make the challenges work, is there a recommended way to develop under such circumstances?

  3. Finally, I think this has come up before, but is this safe to keep exposed outside my LAN? I suspect that disabling the registration endpoint solves most security concerns, but just to make sure I am understanding the process: lets say I set up my primary DNS to point to acme-dns as described in the readme, and the register an account to update it. Assuming I left the registration endpoint turned on, couldnt anyone overwrite the/add a registration for my domain and create a certificate for it?

Option to drop privileges after binding to port 53?

From the readme:

Please note that acme-dns needs to open a privileged port (53, domain), so it needs to be run with elevated privileges

Is it possible to add a config option to drop privileges after starting the app? A daemon running as root scares me :)

Add Option to Use staging Let'sEncryptServer during setup

Hi!

Thanks for the great piece of software. During setup, I was trying to figure out my firewall for allowing DNS through properly, and I hit the production requests/hr limit accidentally on Let'sEncrypt servers. It would be nice to be able to change to their staging servers during setup and validation of firewall/ethernet settings. Maybe a config option?

Rewrite HTTP API to get rid of Iris

The very light usage of HTTP API features can be done using something a lot more lightweight than Iris. The framework also has hard time keeping a stable API, so it causes a lot of unnecessary work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.