- {{ $.username }}@{{ .Name }} |
+ {{ .User }}@{{ .Name }} |
|
|
diff --git a/handlers/README.md b/handlers/README.md
new file mode 100644
index 0000000..cef2157
--- /dev/null
+++ b/handlers/README.md
@@ -0,0 +1,37 @@
+# Certman
+Certman is a simple certificate manager web service for OpenVPN.
+
+## Installation
+### Binary
+There are prebuilt binary files for this application. They are statically
+linked and have no additional dependencies. Supported plattforms are:
+ * Windows (XP and up)
+ * Linux (2.6.16 and up)
+ * Linux ARM (for raspberry pi, 3.0 and up)
+Simply download them from the "artifacts" section of this project.
+### Docker
+A prebuilt docker image (10MB) is available:
+```bash
+docker pull docker.klink.asia/paul/certman
+```
+### From Source-Docker
+You can easily build your own docker image from source
+```bash
+docker build -t docker.klink.asia/paul/certman .
+```
+
+## Configuration
+Certman assumes the root certificates of the VPN CA are located in the same
+directory as the binary, If that is not the case you need to copy over the
+`ca.crt` and `ca.key` files before you are able to generate certificates
+with this tool.
+
+Additionally, the project is configured by the following environment
+variables:
+ * `OAUTH2_CLIENT_ID` the Client ID, assigned during client registration
+ * `OAUTH2_CLIENT_SECRET` the Client secret, assigned during client registration
+ * `OAUTH2_AUTH_URL` the URL to the "/authorize" endpoint of the identity provider
+ * `OAUTH2_TOKEN_URL` the URL to the "/token" endpoint of the identity provider
+ * `OAUTH2_REDIRECT_URL` the redirect URL used by the app, usually the hostname suffixed by "/login/oauth2/redirect"
+ * `USER_ENDPOINT` the URL to the Identity provider user endpoint, for gitlab this is "/api/v4/user". The "username" attribute of the returned JSON will used for authentication.
+ * `APP_KEY` random ASCII string, 32 characters in length. Used for cookie generation.
\ No newline at end of file
diff --git a/handlers/auth.go b/handlers/auth.go
index f2e9991..5f1c7dc 100644
--- a/handlers/auth.go
+++ b/handlers/auth.go
@@ -12,25 +12,14 @@ import (
"git.klink.asia/paul/certman/services"
)
-var GitlabConfig = &oauth2.Config{
- ClientID: os.Getenv("OAUTH2_CLIENT_ID"),
- ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"),
- Scopes: []string{"read_user"},
- RedirectURL: os.Getenv("HOST") + "/login/oauth2/redirect",
- Endpoint: oauth2.Endpoint{
- AuthURL: os.Getenv("OAUTH2_AUTH_URL"),
- TokenURL: os.Getenv("OAUTH2_TOKEN_URL"),
- },
-}
-
-func OAuth2Endpoint(p *services.Provider) http.HandlerFunc {
+func OAuth2Endpoint(p *services.Provider, config *oauth2.Config) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
v := views.NewWithSession(req, p.Sessions)
code := req.FormValue("code")
// exchange code for token
- accessToken, err := GitlabConfig.Exchange(oauth2.NoContext, code)
+ accessToken, err := config.Exchange(oauth2.NoContext, code)
if err != nil {
fmt.Println(err)
http.NotFound(w, req)
@@ -39,9 +28,9 @@ func OAuth2Endpoint(p *services.Provider) http.HandlerFunc {
if accessToken.Valid() {
// generate a client using the access token
- httpClient := GitlabConfig.Client(oauth2.NoContext, accessToken)
+ httpClient := config.Client(oauth2.NoContext, accessToken)
- apiRequest, err := http.NewRequest("GET", "https://git.klink.asia/api/v4/user", nil)
+ apiRequest, err := http.NewRequest("GET", os.Getenv("USER_ENDPOINT"), nil)
if err != nil {
v.RenderError(w, http.StatusNotFound)
return
@@ -78,9 +67,9 @@ func OAuth2Endpoint(p *services.Provider) http.HandlerFunc {
}
}
-func GetLoginHandler(p *services.Provider) http.HandlerFunc {
+func GetLoginHandler(p *services.Provider, config *oauth2.Config) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
- authURL := GitlabConfig.AuthCodeURL("", oauth2.AccessTypeOnline)
+ authURL := config.AuthCodeURL("", oauth2.AccessTypeOnline)
http.Redirect(w, req, authURL, http.StatusFound)
}
}
diff --git a/handlers/cert.go b/handlers/cert.go
index 551f180..94cd859 100644
--- a/handlers/cert.go
+++ b/handlers/cert.go
@@ -8,10 +8,12 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
+ "html/template"
"io/ioutil"
"log"
"math/big"
"net/http"
+ "strings"
"time"
"git.klink.asia/paul/certman/models"
@@ -27,7 +29,7 @@ func ListClientsHandler(p *services.Provider) http.HandlerFunc {
username := p.Sessions.GetUsername(req)
- clients, _ := p.DB.ListClientsForUser(username, 100, 0)
+ clients, _ := p.ClientCollection.ListClientsForUser(username)
v.Vars["Clients"] = clients
v.Render(w, "client_list")
@@ -39,7 +41,8 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc {
username := p.Sessions.GetUsername(req)
certname := req.FormValue("certname")
- if !IsByteLength(certname, 2, 64) || !IsAlphanumeric(certname) {
+ // Validate certificate Name
+ if !IsByteLength(certname, 2, 64) || !IsDNSName(certname) {
p.Sessions.Flash(w, req,
services.Flash{
Type: "danger",
@@ -50,6 +53,10 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc {
return
}
+ // lowercase the certificate name, to avoid problems with the case
+ // insensitive matching inside OpenVPN
+ certname = strings.ToLower(certname)
+
// Load CA master certificate
caCert, caKey, err := loadX509KeyPair("ca.crt", "ca.key")
if err != nil {
@@ -78,13 +85,14 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc {
// Initialize new client config
client := models.Client{
Name: certname,
+ CreatedAt: time.Now(),
PrivateKey: x509.MarshalPKCS1PrivateKey(key),
Cert: derBytes,
User: username,
}
// Insert client into database
- if err := p.DB.CreateClient(&client); err != nil {
+ if err := p.ClientCollection.CreateClient(&client); err != nil {
log.Println(err.Error())
p.Sessions.Flash(w, req,
services.Flash{
@@ -107,6 +115,40 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc {
}
}
+func DeleteCertHandler(p *services.Provider) http.HandlerFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ v := views.New(req)
+ // detemine own username
+ username := p.Sessions.GetUsername(req)
+ name := chi.URLParam(req, "name")
+
+ client, err := p.ClientCollection.GetClientByNameUser(name, username)
+ if err != nil {
+ v.RenderError(w, http.StatusNotFound)
+ return
+ }
+
+ err = p.ClientCollection.DeleteClient(client.ID)
+ if err != nil {
+ p.Sessions.Flash(w, req,
+ services.Flash{
+ Type: "danger",
+ Message: "Failed to delete certificate",
+ },
+ )
+ http.Redirect(w, req, "/certs", http.StatusFound)
+ }
+
+ p.Sessions.Flash(w, req,
+ services.Flash{
+ Type: "success",
+ Message: template.HTML(fmt.Sprintf("Successfully deleted client