Skip to content

Commit

Permalink
Add files
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrsagen committed Oct 26, 2018
1 parent b3f81a2 commit 07175b4
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

bin/
tmp/
conf/
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# go-mikrotik-certman
# MikroTik Certificate Manager
Certificate updater for MikroTik routers. Useful for updating Let's Encrypt certificates on a MikroTik router.

## Usage

```
Usage of go-mikrotik-certman:
-c string
Configuration file (default "config.json")
```

Check out the sample configuration file and modify it to your needs. By default, the tool will try to load `config.json` in the current working directory. A custom configuration file path can also be provided using the command-line flag `-c`.

**Note that certificate and private key MUST be in PEM format!**
16 changes: 16 additions & 0 deletions config.sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Router": {
"APIAddr": "10.0.0.1:8728",
"FTPAddr": "10.0.0.1:21",
"Username": "certman",
"Password": "VerySecurePassword!",
"PostScripts": ["update_cert"]
},
"Cert": {
"Name": "your_certificate_name",
"LocalCertPath": "/etc/letsencrypt/live/example.com/fullchain.pem",
"LocalKeyPath": "/etc/letsencrypt/live/example.com/privkey.pem",
"RemoteCertPath": "certman/cert.pem",
"RemoteKeyPath": "certman/key.pem"
}
}
218 changes: 218 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package main

import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
"time"

"github.com/jlaffaye/ftp"

"gopkg.in/routeros.v2"
)

type config struct {
Router struct {
APIAddr, FTPAddr string
Username, Password string

PreScripts, PostScripts []string
}
Cert struct {
Name string
Passphrase string

LocalCertPath, LocalKeyPath string
RemoteCertPath, RemoteKeyPath string
}
}

type router struct {
ftp *ftp.ServerConn
api *routeros.Client
}

func main() {
c, r := new(config), new(router)

configPath := flag.String("c", "config.json", "Configuration file")
flag.Parse()

fmt.Printf("reading config... ")
configBuf, err := ioutil.ReadFile(*configPath)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if err = json.Unmarshal(configBuf, &c); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
fmt.Println("done.")

fmt.Printf("opening cert file... ")
fileCert, err := os.Open(c.Cert.LocalCertPath)
if os.IsNotExist(err) {
fmt.Printf("fail: %v\n", err)
return
}
defer fileCert.Close()
fmt.Println("done.")

fmt.Printf("opening key file... ")
fileKey, err := os.Open(c.Cert.LocalKeyPath)
if os.IsNotExist(err) {
fmt.Printf("fail: %v\n", err)
return
}
defer fileKey.Close()
fmt.Println("done.")

fmt.Printf("getting cert fingerprint... ")
certBytes, err := ioutil.ReadAll(fileCert)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if _, err = fileCert.Seek(0, 0); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
certBlock, _ := pem.Decode(certBytes)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
certSHA := sha256.Sum256(cert.Raw)
certFingerprint := hex.EncodeToString(certSHA[:])
fmt.Println("done.")

fmt.Printf("connecting to router ftp... ")
if r.ftp, err = ftp.Dial(c.Router.FTPAddr); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if err = r.ftp.Login(c.Router.Username, c.Router.Password); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
fmt.Println("done.")

fmt.Printf("uploading files to router... ")
if err = r.ftp.Stor(c.Cert.RemoteCertPath, fileCert); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if err = r.ftp.Stor(c.Cert.RemoteKeyPath, fileKey); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if err = r.ftp.Logout(); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
fmt.Println("done.")

fmt.Printf("wait for router to flush files to flash... ")
time.Sleep(time.Second)
fmt.Println("done.")

fmt.Printf("connecting to router api... ")
if r.api, err = routeros.Dial(c.Router.APIAddr, c.Router.Username, c.Router.Password); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
defer r.api.Close()
fmt.Println("done.")

if len(c.Router.PreScripts) > 0 {
fmt.Printf("running pre-import scripts... ")
for _, scriptName := range c.Router.PreScripts {
reply, err := r.api.Run("/system/script/print", "?name="+scriptName)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if len(reply.Re) == 0 {
fmt.Printf("fail: could not find script \"%s\" on router\n", scriptName)
return
}
if _, err := r.api.Run("/system/script/run", "=.id="+reply.Re[0].Map[".id"]); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
}
fmt.Println("done.")
}

fmt.Printf("importing keypair on router... ")

// Remove any existing keypair with same certificate fingerprint
reply, err := r.api.Run("/certificate/print", "?fingerprint="+certFingerprint)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if len(reply.Re) > 0 {
if _, err = r.api.Run("/certificate/remove", "=.id="+reply.Re[0].Map[".id"]); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
}

// Import new keypair
if _, err = r.api.Run("/certificate/import", "=file-name="+c.Cert.RemoteCertPath, "=passphrase="+c.Cert.Passphrase); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if _, err = r.api.Run("/certificate/import", "=file-name="+c.Cert.RemoteKeyPath, "=passphrase="+c.Cert.Passphrase); err != nil {
fmt.Printf("fail: %v\n", err)
return
}

// Find newly imported keypair ID
reply, err = r.api.Run("/certificate/print", "?fingerprint="+certFingerprint)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if len(reply.Re) == 0 {
fmt.Println("fail: could not find imported keypair")
return
}

// Update keypair name
if _, err = r.api.Run("/certificate/set", "=.id="+reply.Re[0].Map[".id"], "=name="+c.Cert.Name); err != nil {
fmt.Printf("fail: %v\n", err)
return
}

fmt.Println("done.")

if len(c.Router.PostScripts) > 0 {
fmt.Printf("running post-import scripts... ")
for _, scriptName := range c.Router.PostScripts {
reply, err := r.api.Run("/system/script/print", "?name="+scriptName)
if err != nil {
fmt.Printf("fail: %v\n", err)
return
}
if len(reply.Re) == 0 {
fmt.Printf("fail: could not find script \"%s\" on router\n", scriptName)
return
}
if _, err := r.api.Run("/system/script/run", "=.id="+reply.Re[0].Map[".id"]); err != nil {
fmt.Printf("fail: %v\n", err)
return
}
}
fmt.Println("done.")
}
}

0 comments on commit 07175b4

Please sign in to comment.