Newer
Older
"github.com/docopt/docopt-go"
"github.com/go-ini/ini"
"github.com/tidwall/gjson"
)
var (
appVersion string
buildTime string
CertBase string
KeyBase string
GroupName string
RedisBaseURL string
VaultBaseURL string
certificateDestination string
fullchainDestination string
keyDestination string
caDestination string
Type string
)
// check certificates
func checkCerificates(dnsname string, certificate string, fullchain string, ca string, key string, days int, fail bool) bool {
Seconds := days * 86400
daysNumber := time.Now().Local().Add(time.Second * time.Duration(Seconds))
//fmt.Printf(daysNumber)
certPEM, err := ioutil.ReadFile(certificate)
if err != nil {
if fail == true {
} else {
return false
}
}
certFullchainPEM, err := ioutil.ReadFile(fullchain)
if err != nil {
if fail == true {
} else {
return false
}
}
rootPEM, err := ioutil.ReadFile(ca)
if err != nil {
if fail == true {
} else {
return false
}
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
if fail == true {
fmt.Printf("[ERROR] failed to parse root certificate\n")
os.Exit(255)
} else {
return false
}
}
block, _ := pem.Decode([]byte(certPEM))
if block == nil {
if fail == true {
fmt.Printf("[ERROR] failed to parse certificate PEM\n")
os.Exit(255)
} else {
return false
}
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
if fail == true {
fmt.Printf("[ERROR] failed to parse certificate %v\n", err)
os.Exit(255)
} else {
return false
}
}
fullchainBlock, _ := pem.Decode([]byte(certFullchainPEM))
if fullchainBlock == nil {
if fail == true {
fmt.Printf("[ERROR] failed to parse certificate PEM\n")
os.Exit(255)
} else {
return false
}
}
fullchainCert, fullchainErr := x509.ParseCertificate(fullchainBlock.Bytes)
if fullchainErr != nil {
if fail == true {
fmt.Printf("[ERROR] failed to parse certificate %v\n", fullchainErr)
os.Exit(255)
} else {
return false
}
}
opts := x509.VerifyOptions{
Roots: roots,
DNSName: dnsname,
CurrentTime: daysNumber,
Intermediates: x509.NewCertPool(),
}
if _, err := cert.Verify(opts); err != nil {
if fail == true {
fmt.Printf("[ERROR] failed to parse certificate %v\n", err.Error())
os.Exit(255)
} else {
return false
}
}
if _, fullchainErr := fullchainCert.Verify(opts); fullchainErr != nil {
if fail == true {
} else {
return false
}
}
return true
}
// get redis key
func GetRedisKey(redisurl string, redistoken string) string {
client := &http.Client{}
req, err := http.NewRequest("GET", redisurl, nil)
req.SetBasicAuth("redis", redistoken)
resp, err := client.Do(req)
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
fmt.Printf("[ERROR] Fail to read %v: %v\n", redisurl, err)
os.Exit(255)
// get Vault key
func GetVaultKey(vaulturl string, vaulttoken string) string {
vaultClient := &http.Client{}
req, err := http.NewRequest("GET", vaulturl, nil)
req.Header.Add("X-vault-token", vaulttoken)
resp, err := vaultClient.Do(req)
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
fmt.Printf("[ERROR] Fail to read %v: %v\n", vaulturl, err)
os.Exit(255)
}
return gjson.Get(string(body), "data.value").String()
}
// create directory structure and write certificate to file
func WriteToFile(content string, destination string, groupname string, filemode os.FileMode, dirmode os.FileMode) {
baseDir := filepath.Dir(destination)
if _, err := os.Stat(baseDir); os.IsNotExist(err) {
os.MkdirAll(baseDir, 0755)
}
os.Chmod(baseDir, dirmode)
file, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, filemode)
if err != nil {
fmt.Printf("[ERROR] %v cannot be created\n", destination)
os.Exit(255)
}
fmt.Fprintf(file, "%v\n", content)
file.Close()
}
// ReadOSRelease from /etc/os-release
func ReadOSRelease(configfile string) map[string]string {
ConfigParams["ID"] = "unknown"
} else {
ConfigParams["ID"] = cfg.Section("").Key("ID").String()
}
return ConfigParams
}
func main() {
OSInfo := ReadOSRelease("/etc/os-release")
OSRelease := OSInfo["ID"]
if OSRelease == "centos" || OSRelease == "rhel" {
CertBase = "/etc/pki/tls/certs"
KeyBase = "/etc/pki/tls/private"
GroupName = "root"
} else if OSRelease == "ubuntu" || OSRelease == "debian" {
CertBase = "/etc/ssl/certs"
KeyBase = "/etc/ssl/private"
GroupName = "ssl-cert"
} else if OSRelease == "arch" {
CertBase = "/etc/ssl/certs"
KeyBase = "/etc/ssl/private"
GroupName = "root"
} else if OSRelease == "unknown" {
CertBase = "/PATH/TO/CERTIFICATE"
KeyBase = "/PATH/TO/PRIV/KEY"
GroupName = "root"
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
}
usage := fmt.Sprintf(`ACME Downloader:
- fetches and stores a given Certificate, Full Chain, CA and Private Key
Usage:
acme-downloader --redis-token=REDISTOKEN --vault-token=VAULTTOKEN --cert-name=CERTNAME --team-name=TEAMNAME [--days=DAYS] [--type=TYPE] [--cert-destination=CERTDESTINATION] [--fullchain-destination=FULLCHAINDESTINATION] [--key-destination=KEYDESTINATION] [--ca-destination=CADESTINATION]
acme-downloader -v | --version
acme-downloader -b | --build
acme-downloader -h | --help
Options:
-h --help Show this screen
-v --version Print version exit
-b --build Print version and build information and exit
--redis-token=REDISTOKEN Redis access token
--vault-token=VAULTTOKEN Vault access token
--cert-name=CERTNAME Certificate name
--team-name=TEAMNAME Team name: swd, dream_team, it, ne, ti...
--days=DAYS Days before expiration [default: 30]
--type=TYPE Type, EV or OV [default: EV]
--cert-destination=CERTDESTINATION Cert Destination [default: %v/<cert-name>.crt]
--fullchain-destination=FULLCHAINDESTINATION Full Chain Destination[default: %v/<cert-name>_fullchain.crt]
--key-destination=KEYDESTINATION Key Destination [default: %v/<cert-name>.key]
--ca-destination=CADESTINATION CA Destination [default: %v/COMODO_<type>.crt]
`, CertBase, CertBase, KeyBase, CertBase)
// Annoyingly docopt tries to use 'version' the way he wants and I am using build
arguments, _ := docopt.Parse(usage, nil, true, appVersion, false)
if arguments["--build"] == true {
fmt.Printf("acme-downloader version: %v, built on: %v\n", appVersion, buildTime)
os.Exit(0)
}
VaultToken := arguments["--vault-token"].(string)
CertName := arguments["--cert-name"].(string)
CertNameUndercored := strings.Replace(CertName, ".", "_", -1)
TeamName := arguments["--team-name"].(string)
RedisToken := arguments["--redis-token"].(string)
Type = arguments["--type"].(string)
DayString := arguments["--days"].(string)
Days, daysErr := strconv.Atoi(DayString)
if daysErr != nil {
RedisBaseURL = "https://redis.geant.org/GET"
VaultBaseURL = "https://vault.geant.org/v1"
VaultURL := fmt.Sprintf("%v/%v/%v/vault_%v_key", VaultBaseURL, TeamName, CertName, CertNameUndercored)
RedisCertURL := fmt.Sprintf("%v/%v:%v:redis_%v_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)
RedisCAURL := fmt.Sprintf("%v/%v:%v:redis_%v_chain_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)
RedisFullChainURL := fmt.Sprintf("%v/%v:%v:redis_%v_fullchain_pem.txt", RedisBaseURL, TeamName, CertName, CertNameUndercored)
tmpCertificateDestination := "/tmp/amce_cert.pem"
tmpFullchainDestination := "/tmp/amce_fullchain.pem"
tmpCaDestination := "/tmp/amce_ca.pem"
tmpKeyDestination := "/tmp/amce_key.pem"
if arguments["--cert-destination"] == fmt.Sprintf("%v/<cert-name>.crt", CertBase) {
certificateDestination = fmt.Sprintf("%v/%v.crt", CertBase, CertName)
} else {
certificateDestination = arguments["--cert-destination"].(string)
}
if arguments["--fullchain-destination"] == fmt.Sprintf("%v/<cert-name>_fullchain.crt", CertBase) {
fullchainDestination = fmt.Sprintf("%v/%v_fullchain.crt", CertBase, CertName)
} else {
fullchainDestination = arguments["--fullchain-destination"].(string)
}
if arguments["--ca-destination"] == fmt.Sprintf("%v/COMODO_<type>.crt", CertBase) {
caDestination = fmt.Sprintf("%v/COMODO_%v.crt", CertBase, Type)
} else {
caDestination = arguments["--ca-destination"].(string)
}
if arguments["--key-destination"] == fmt.Sprintf("%v/<cert-name>.key", KeyBase) {
keyDestination = fmt.Sprintf("%v/%v.key", KeyBase, CertName)
} else {
keyDestination = arguments["--key-destination"].(string)
}
// check if there is a certificate installed and it is valid
existingCert := checkCerificates(CertName, certificateDestination, fullchainDestination, caDestination, keyDestination, Days, false)
if existingCert == true {
certificate := GetRedisKey(RedisCertURL, RedisToken)
ca := GetRedisKey(RedisCAURL, RedisToken)
fullChain := GetRedisKey(RedisFullChainURL, RedisToken)
WriteToFile(certificate, tmpCertificateDestination, GroupName, 0644, 0755)
WriteToFile(fullChain, tmpFullchainDestination, GroupName, 0644, 0755)
WriteToFile(ca, tmpCaDestination, GroupName, 0644, 0755)
WriteToFile(privKey, tmpKeyDestination, GroupName, 0640, 0750)
tempCertSlice := []string{tmpCertificateDestination, tmpFullchainDestination, tmpCaDestination, tmpKeyDestination}
newCert := checkCerificates(CertName, tmpCertificateDestination, tmpFullchainDestination, tmpCaDestination, tmpKeyDestination, Days, false)
if newCert == false {
fmt.Printf("the certificates are malformed. Skippping installation\n")
for _, element := range tempCertSlice {
os.Remove(element)
}
os.Exit(255)
WriteToFile(certificate, certificateDestination, GroupName, 0644, 0755)
WriteToFile(fullChain, fullchainDestination, GroupName, 0644, 0755)
WriteToFile(ca, caDestination, GroupName, 0644, 0755)
WriteToFile(privKey, keyDestination, GroupName, 0640, 0750)
fmt.Printf("installed: %v\n", certificateDestination)
fmt.Printf("installed: %v\n", caDestination)
fmt.Printf("installed: %v\n", fullchainDestination)
fmt.Printf("installed: %v\n", keyDestination)
}