├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── ini.sh ├── run.sh └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Prevent idiots like myself from checking in keys 27 | *.crt 28 | *.key 29 | *.csr 30 | /ssl-tester 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM certbot/dns-cloudflare:latest 2 | 3 | LABEL maintainer Chris Short 4 | 5 | ARG cf_domain 6 | ARG cf_email 7 | ARG cf_key 8 | 9 | ENV CF_DOMAIN $cf_domain 10 | ENV CF_EMAIL $cf_email 11 | ENV CF_KEY $cf_key 12 | ENV GOPATH /go 13 | ENV PATH /go/bin:/usr/local/go/bin:$PATH 14 | 15 | ADD ini.sh /root/ 16 | ADD run.sh /root/ 17 | #RUN ls -l /root/*.sh ; 18 | 19 | EXPOSE 8080 8443 20 | 21 | COPY . /go/src/github.com/chris-short/ssl-tester 22 | 23 | RUN set -x \ 24 | && apk add --no-cache --virtual .build-deps \ 25 | ca-certificates \ 26 | go \ 27 | git \ 28 | gcc \ 29 | libc-dev \ 30 | libgcc \ 31 | && cd /go/src/github.com/chris-short/ssl-tester \ 32 | && go build -o /usr/bin/ssl-tester . \ 33 | && apk del .build-deps \ 34 | && rm -rf /go \ 35 | && echo "Build complete." 36 | 37 | ENTRYPOINT [ "/root/run.sh" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris Short 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # STOLEN FROM JESS FRAZELLE (https://github.com/jessfraz/ghb0t/blob/d001182ea04a3d7d6cbf0d3101ad915bec1466ee/Makefile) 2 | # I DON'T KNOW IF THIS STILL WORKS --cshort 2019-02-02 3 | 4 | # Set an output prefix, which is the local directory if not specified 5 | PREFIX?=$(shell pwd) 6 | 7 | # Setup name variables for the package/tool 8 | NAME := ssl-tester 9 | PKG := github.com/chris-short/$(NAME) 10 | 11 | # Set any default go build tags 12 | BUILDTAGS := 13 | 14 | # Set the build dir, where built cross-compiled binaries will be output 15 | BUILDDIR := ${PREFIX}/cross 16 | 17 | # Populate version variables 18 | # Add to compile time flags 19 | VERSION := $(shell cat VERSION) 20 | GITCOMMIT := $(shell git rev-parse --short HEAD) 21 | GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no) 22 | ifneq ($(GITUNTRACKEDCHANGES),) 23 | GITCOMMIT := $(GITCOMMIT)-dirty 24 | endif 25 | CTIMEVAR=-X $(PKG)/version.GITCOMMIT=$(GITCOMMIT) -X $(PKG)/version.VERSION=$(VERSION) 26 | GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)" 27 | GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" 28 | 29 | # List the GOOS and GOARCH to build 30 | GOOSARCHES = darwin/amd64 darwin/386 freebsd/amd64 freebsd/386 linux/arm linux/arm64 linux/amd64 linux/386 solaris/amd64 windows/amd64 windows/386 31 | 32 | all: clean build fmt lint test staticcheck vet install ## Runs a clean, build, fmt, lint, test, staticcheck, vet and install 33 | 34 | .PHONY: build 35 | build: $(NAME) ## Builds a dynamic executable or package 36 | 37 | $(NAME): *.go VERSION 38 | @echo "+ $@" 39 | go build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o $(NAME) . 40 | 41 | .PHONY: static 42 | static: ## Builds a static executable 43 | @echo "+ $@" 44 | CGO_ENABLED=0 go build \ 45 | -tags "$(BUILDTAGS) static_build" \ 46 | ${GO_LDFLAGS_STATIC} -o $(NAME) . 47 | 48 | .PHONY: fmt 49 | fmt: ## Verifies all files have men `gofmt`ed 50 | @echo "+ $@" 51 | @gofmt -s -l . | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr 52 | 53 | .PHONY: lint 54 | lint: ## Verifies `golint` passes 55 | @echo "+ $@" 56 | @golint ./... | grep -v '.pb.go:' | grep -v vendor | tee /dev/stderr 57 | 58 | .PHONY: test 59 | test: ## Runs the go tests 60 | @echo "+ $@" 61 | @go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v vendor) 62 | 63 | .PHONY: vet 64 | vet: ## Verifies `go vet` passes 65 | @echo "+ $@" 66 | @go vet $(shell go list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr 67 | 68 | .PHONY: staticcheck 69 | staticcheck: ## Verifies `staticcheck` passes 70 | @echo "+ $@" 71 | @staticcheck $(shell go list ./... | grep -v vendor) | grep -v '.pb.go:' | tee /dev/stderr 72 | 73 | .PHONY: install 74 | install: ## Installs the executable or package 75 | @echo "+ $@" 76 | go install -a -tags "$(BUILDTAGS)" ${GO_LDFLAGS} . 77 | 78 | define buildpretty 79 | mkdir -p $(BUILDDIR)/$(1)/$(2); 80 | GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build \ 81 | -o $(BUILDDIR)/$(1)/$(2)/$(NAME) \ 82 | -a -tags "$(BUILDTAGS) static_build netgo" \ 83 | -installsuffix netgo ${GO_LDFLAGS_STATIC} .; 84 | md5sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).md5; 85 | sha256sum $(BUILDDIR)/$(1)/$(2)/$(NAME) > $(BUILDDIR)/$(1)/$(2)/$(NAME).sha256; 86 | endef 87 | 88 | .PHONY: cross 89 | cross: *.go VERSION ## Builds the cross-compiled binaries, creating a clean directory structure (eg. GOOS/GOARCH/binary) 90 | @echo "+ $@" 91 | $(foreach GOOSARCH,$(GOOSARCHES), $(call buildpretty,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH)))) 92 | 93 | define buildrelease 94 | GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build \ 95 | -o $(BUILDDIR)/$(NAME)-$(1)-$(2) \ 96 | -a -tags "$(BUILDTAGS) static_build netgo" \ 97 | -installsuffix netgo ${GO_LDFLAGS_STATIC} .; 98 | md5sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).md5; 99 | sha256sum $(BUILDDIR)/$(NAME)-$(1)-$(2) > $(BUILDDIR)/$(NAME)-$(1)-$(2).sha256; 100 | endef 101 | 102 | .PHONY: release 103 | release: *.go VERSION ## Builds the cross-compiled binaries, naming them in such a way for release (eg. binary-GOOS-GOARCH) 104 | @echo "+ $@" 105 | $(foreach GOOSARCH,$(GOOSARCHES), $(call buildrelease,$(subst /,,$(dir $(GOOSARCH))),$(notdir $(GOOSARCH)))) 106 | 107 | .PHONY: bump-version 108 | BUMP := patch 109 | bump-version: ## Bump the version in the version file. Set BUMP to [ patch | major | minor ] 110 | @go get -u github.com/jessfraz/junk/sembump # update sembump tool 111 | $(eval NEW_VERSION = $(shell sembump --kind $(BUMP) $(VERSION))) 112 | @echo "Bumping VERSION from $(VERSION) to $(NEW_VERSION)" 113 | echo $(NEW_VERSION) > VERSION 114 | @echo "Updating links to download binaries in README.md" 115 | sed -i s/$(VERSION)/$(NEW_VERSION)/g README.md 116 | git add VERSION README.md 117 | git commit -vsam "Bump version to $(NEW_VERSION)" 118 | @echo "Run make tag to create and push the tag for new version $(NEW_VERSION)" 119 | 120 | .PHONY: tag 121 | tag: ## Create a new git tag to prepare to build a release 122 | git tag -sa $(VERSION) -m "$(VERSION)" 123 | @echo "Run git push origin $(VERSION) to push your new tag to GitHub and trigger a travis build." 124 | 125 | .PHONY: clean 126 | clean: ## Cleanup any build binaries or packages 127 | @echo "+ $@" 128 | $(RM) $(NAME) 129 | $(RM) -r $(BUILDDIR) 130 | 131 | .PHONY: help 132 | help: 133 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/chris-short/ssl-tester)](https://goreportcard.com/report/github.com/chris-short/ssl-tester) 2 | [![GoDoc](https://godoc.org/github.com/chris-short/ssl-tester?status.svg)](https://godoc.org/github.com/chris-short/ssl-tester) 3 | [![Build Status](https://travis-ci.org/chris-short/ssl-tester.svg?branch=master)](https://travis-ci.org/chris-short/ssl-tester) 4 | [![Docker Repository on Quay](https://quay.io/repository/chrisshort/ssl-tester/status "Docker Repository on Quay")](https://quay.io/repository/chrisshort/ssl-tester) 5 | [![SSL Rating](https://sslbadge.org/?domain=ssl-tester.chrisshort.net)](https://www.ssllabs.com/ssltest/analyze.html?d=ssl-tester.chrisshort.net) 6 | ![Twitter Follow](https://img.shields.io/twitter/follow/ChrisShort?style=social) 7 | ![GitHub followers](https://img.shields.io/github/followers/chris-short?style=social) 8 | ![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/devopsish?style=social) 9 | 10 | # ssl-tester 11 | 12 | ## Description 13 | 14 | A small Go app intended to help troubleshoot certificate chains. 15 | 16 | A detailed use case that prompted the creation of this code was featured on [opensource.com](https://opensource.com/article/17/4/testing-certificate-chains-34-line-go-program). I highly recommend reading it. 17 | 18 | ## Requirements 19 | 20 | - go (if you want to modify paths to certificates you will need to run: `go build`) 21 | - Valid TLS keys 22 | - sudo (or root access) to run the ssl-tester server that binds to port 443 23 | 24 | ## Installing 25 | 26 | Installation to your $GOPATH is recommended: 27 | 28 | ``` 29 | go get github.com/chris-short/ssl-tester 30 | ``` 31 | 32 | A public and private key at `/etc/ssl-tester/tls.crt` and `/etc/ssl-tester/tls.key` respectively are expected. These paths can be symlinks to keypairs in another path. 33 | 34 | If you want to compile ssl-tester for another platform you can clone this repo and use `go build`. I encourage you to read Dave Chaney's [Cross compilation with Go](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) to better understand that process. 35 | 36 | ## Container 37 | 38 | To build the container you will need to set environment variables in your local environment and pass them through to the container. 39 | 40 | Container uses Let's Encrypt (certbot) and Cloudflare to obtain DNS. Say what you want about Cloudflare but it's free and good so it's the lowest barrier to entry. 41 | 42 | `docker build --build-arg cf_email=$CF_EMAIL --build-arg cf_key=$CF_KEY --build-arg cf_domain=$CF_DOMAIN -t quay.io/chrisshort/ssl-tester .` 43 | 44 | Yes, this README is not great. Check me, Boo. 45 | 46 | ## Caveats 47 | 48 | You might be able to use it to serve a frontend for a small service too if you'd so desire. Pull requests welcome! 49 | 50 | ## License 51 | 52 | MIT 53 | 54 | ## Author 55 | 56 | Chris Short 57 | https://chrisshort.net 58 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v0.29 -------------------------------------------------------------------------------- /ini.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat < /root/.credentials/cf.ini 4 | # Cloudflare API credentials used by Certbot 5 | dns_cloudflare_email = $CF_EMAIL 6 | dns_cloudflare_api_key = $CF_KEY 7 | EOF 8 | 9 | chmod 0600 /root/.credentials/cf.ini -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p /etc/ssl-tester && 4 | # && ls -ld /etc/ssl-tester \ 5 | /root/ini.sh && 6 | # && cat /root/cf.ini \ 7 | certbot register --agree-tos --eff-email --email ${CF_EMAIL} && 8 | certbot certonly \ 9 | --dns-cloudflare \ 10 | --dns-cloudflare-propagation-seconds 5 \ 11 | --dns-cloudflare-credentials /root/cf.ini \ 12 | -d ${CF_DOMAIN} && 13 | # && ls -l /etc/letsencrypt/live/* \ 14 | ln -s /etc/letsencrypt/live/${CF_DOMAIN}/fullchain.pem /etc/ssl-tester/fullchain.pem && 15 | ln -s /etc/letsencrypt/live/${CF_DOMAIN}/privkey.pem /etc/ssl-tester/privkey.pem && 16 | rm -f /root/ini.sh && 17 | rm -f /root/cf.ini 18 | 19 | ssl-tester -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func redirect(w http.ResponseWriter, req *http.Request) { 10 | target := "https://" + req.Host + req.URL.Path 11 | if len(req.URL.RawQuery) > 0 { 12 | target += "?" + req.URL.RawQuery 13 | } 14 | log.Printf("redirect to: %s", target) 15 | http.Redirect(w, req, target, 16 | http.StatusTemporaryRedirect) 17 | } 18 | 19 | func main() { 20 | go http.ListenAndServe(":8080", http.HandlerFunc(redirect)) 21 | mux := http.NewServeMux() 22 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 23 | w.Header().Add("Strict-Transport-Security", "max-age=63072000;") 24 | w.Write([]byte("

Hello World!

\n

👋👋👋👋👋

\nSource: https://github.com/chris-short/ssl-tester")) 25 | }) 26 | cfg := &tls.Config{ 27 | MinVersion: tls.VersionTLS12, 28 | CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 29 | PreferServerCipherSuites: true, 30 | CipherSuites: []uint16{ 31 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 32 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 33 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 34 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 35 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 36 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 37 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 38 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 39 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 40 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 41 | }, 42 | } 43 | srv := &http.Server{ 44 | Addr: ":8443", 45 | Handler: mux, 46 | TLSConfig: cfg, 47 | TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), 48 | } 49 | log.Fatal(srv.ListenAndServeTLS("/etc/ssl-tester/fullchain.pem", "/etc/ssl-tester/privkey.pem")) 50 | } 51 | --------------------------------------------------------------------------------