summaryrefslogtreecommitdiff
path: root/acme.go
diff options
context:
space:
mode:
Diffstat (limited to 'acme.go')
-rw-r--r--acme.go105
1 files changed, 105 insertions, 0 deletions
diff --git a/acme.go b/acme.go
new file mode 100644
index 0000000..bbde2e9
--- /dev/null
+++ b/acme.go
@@ -0,0 +1,105 @@
+// Copyright 2016 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "time"
+)
+
+type acmeCacheEntry struct {
+ backend string
+ expires time.Time
+}
+
+type ACME struct {
+ backends []string
+ // *.acme.invalid domain to cache entry
+ cache map[string]acmeCacheEntry
+}
+
+func (s *ACME) Match(hostname string) string {
+ c := s.cache[hostname]
+ if time.Now().Before(c.expires) {
+ return c.backend
+ }
+
+ // Cache entry is either expired or invalid, need to figure out
+ // which backend is the right one.
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ ch := make(chan string, len(s.backends))
+ for _, backend := range s.backends {
+ go tryAcme(ctx, ch, backend, hostname)
+ }
+ for range s.backends {
+ backend := <-ch
+ if backend != "" {
+ s.cache[hostname] = acmeCacheEntry{backend, time.Now().Add(5 * time.Second)}
+ return backend
+ }
+ }
+
+ // No usable backends found :(
+ s.cache[hostname] = acmeCacheEntry{"", time.Now().Add(5 * time.Second)}
+ return ""
+}
+
+func tryAcme(ctx context.Context, ch chan string, backend, hostname string) {
+ var res string
+ var err error
+ defer func() { ch <- res }()
+ defer func() {
+ if err != nil {
+ fmt.Println(err)
+ }
+ }()
+
+ dialer := net.Dialer{Timeout: 10 * time.Second}
+ conn, err := dialer.DialContext(ctx, "tcp", backend)
+ if err != nil {
+ return
+ }
+ defer conn.Close()
+
+ deadline, ok := ctx.Deadline()
+ if ok {
+ conn.SetDeadline(deadline)
+ }
+ client := tls.Client(conn, &tls.Config{
+ ServerName: hostname,
+ InsecureSkipVerify: true,
+ })
+ if err != nil {
+ return
+ }
+ if err = client.Handshake(); err != nil {
+ return
+ }
+
+ certs := client.ConnectionState().PeerCertificates
+ if len(certs) == 0 {
+ return
+ }
+ if err = certs[0].VerifyHostname(hostname); err != nil {
+ return
+ }
+
+ res = backend
+}