Added openapi description

This commit is contained in:
Andrey Meshkov 2019-02-21 17:33:46 +03:00
parent 37a1a98c49
commit 251beb24d3
5 changed files with 165 additions and 71 deletions

2
app.go
View File

@ -173,7 +173,7 @@ func run(args options) {
for { // this is an endless loop for { // this is an endless loop
httpsServer.cond.L.Lock() httpsServer.cond.L.Lock()
// this mechanism doesn't let us through until all conditions are ment // this mechanism doesn't let us through until all conditions are ment
for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until neccessary data is supplied for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
httpsServer.cond.Wait() httpsServer.cond.Wait()
} }
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS)) address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))

View File

@ -63,37 +63,34 @@ type dnsConfig struct {
var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"} var defaultDNS = []string{"tls://1.1.1.1", "tls://1.0.0.1"}
type tlsConfigSettings struct { type tlsConfigSettings struct {
Enabled bool `yaml:"enabled" json:"enabled"` Enabled bool `yaml:"enabled" json:"enabled"` // Enabled is the encryption (DOT/DOH/HTTPS) status
ServerName string `yaml:"server_name" json:"server_name,omitempty"` ServerName string `yaml:"server_name" json:"server_name,omitempty"` // ServerName is the hostname of your HTTPS/TLS server
ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect
PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` // HTTPS port. If 0, HTTPS will be disabled
PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
dnsforward.TLSConfig `yaml:",inline" json:",inline"` dnsforward.TLSConfig `yaml:",inline" json:",inline"`
} }
// field ordering is not important -- these are for API and are recalculated on each run // field ordering is not important -- these are for API and are recalculated on each run
type tlsConfigStatus struct { type tlsConfigStatus struct {
// certificate status ValidCert bool `yaml:"-" json:"valid_cert"` // ValidCert is true if the specified certificates chain is a valid chain of X509 certificates
ValidCert bool `yaml:"-" json:"valid_cert"` ValidChain bool `yaml:"-" json:"valid_chain"` // ValidChain is true if the specified certificates chain is verified and issued by a known CA
ValidChain bool `yaml:"-" json:"valid_chain"` Subject string `yaml:"-" json:"subject,omitempty"` // Subject is the subject of the first certificate in the chain
Subject string `yaml:"-" json:"subject,omitempty"` Issuer string `yaml:"-" json:"issuer,omitempty"` // Issuer is the issuer of the first certificate in the chain
Issuer string `yaml:"-" json:"issuer,omitempty"` NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` // NotBefore is the NotBefore field of the first certificate in the chain
NotBefore time.Time `yaml:"-" json:"not_before,omitempty"` NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` // NotAfter is the NotAfter field of the first certificate in the chain
NotAfter time.Time `yaml:"-" json:"not_after,omitempty"` DNSNames []string `yaml:"-" json:"dns_names"` // DNSNames is the value of SubjectAltNames field of the first certificate in the chain
DNSNames []string `yaml:"-" json:"dns_names"`
StatusCertificate string `yaml:"-" json:"status_cert,omitempty"`
// key status // key status
ValidKey bool `yaml:"-" json:"valid_key"` ValidKey bool `yaml:"-" json:"valid_key"` // ValidKey is true if the key is a valid private key
KeyType string `yaml:"-" json:"key_type,omitempty"` KeyType string `yaml:"-" json:"key_type,omitempty"` // KeyType is one of RSA or ECDSA
// is usable? set by validator // is usable? set by validator
usable bool usable bool
// warnings // warnings
Warning string `yaml:"-" json:"warning,omitempty"` WarningValidation string `yaml:"-" json:"warning_validation,omitempty"` // WarningValidation is a validation warning message with the issue description
WarningValidation string `yaml:"-" json:"warning_validation,omitempty"`
} }
// field ordering is important -- yaml fields will mirror ordering from here // field ordering is important -- yaml fields will mirror ordering from here

View File

@ -1118,7 +1118,7 @@ func validateCertificates(data tlsConfig) tlsConfig {
// clear out status for certificates // clear out status for certificates
data.tlsConfigStatus = tlsConfigStatus{} data.tlsConfigStatus = tlsConfigStatus{}
// check only public certificate separetely from the key // check only public certificate separately from the key
if data.CertificateChain != "" { if data.CertificateChain != "" {
log.Tracef("got certificate: %s", data.CertificateChain) log.Tracef("got certificate: %s", data.CertificateChain)
@ -1194,24 +1194,6 @@ func validateCertificates(data tlsConfig) tlsConfig {
data.NotAfter = notAfter data.NotAfter = notAfter
data.NotBefore = mainCert.NotBefore data.NotBefore = mainCert.NotBefore
data.DNSNames = mainCert.DNSNames data.DNSNames = mainCert.DNSNames
data.StatusCertificate = fmt.Sprintf("Certificate expires on %s", notAfter) //, valid for hostname %s", mainCert.NotAfter, mainCert.Subject.CommonName)
if len(mainCert.DNSNames) == 1 {
data.StatusCertificate += fmt.Sprintf(", valid for hostname %s", mainCert.DNSNames[0])
} else if len(mainCert.DNSNames) > 1 {
data.StatusCertificate += ", valid for hostnames " + strings.Join(mainCert.DNSNames, ", ")
}
// issue a warning if certificate is about to expire
now := time.Now()
if now.AddDate(0, 0, 30).After(notAfter) {
timeLeft := notAfter.Sub(now)
if timeLeft > 0 {
data.Warning = fmt.Sprintf("Your certificate expires in %.0f days, we recommend you update it soon", timeLeft.Hours()/24)
} else {
data.Warning = fmt.Sprintf("Your certificate has expired on %s, we recommend you update it immediatedly", mainCert.NotAfter)
}
}
} }
} }
@ -1322,11 +1304,11 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if data.CertificateChain != "" { if data.CertificateChain != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain)) encoded := base64.StdEncoding.EncodeToString([]byte(data.CertificateChain))
data.CertificateChain = string(encoded) data.CertificateChain = encoded
} }
if data.PrivateKey != "" { if data.PrivateKey != "" {
encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey)) encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
data.PrivateKey = string(encoded) data.PrivateKey = encoded
} }
err := json.NewEncoder(w).Encode(data) err := json.NewEncoder(w).Encode(data)
if err != nil { if err != nil {

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/rand"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -257,34 +256,6 @@ func checkPacketPortAvailable(host string, port int) error {
return err return err
} }
// ------------------------
// random string generation
// ------------------------
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func RandStringBytesMaskImpr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
// --------------------- // ---------------------
// debug logging helpers // debug logging helpers
// --------------------- // ---------------------

View File

@ -2,7 +2,7 @@ swagger: '2.0'
info: info:
title: 'AdGuard Home' title: 'AdGuard Home'
description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.' description: 'AdGuard Home REST API. Admin web interface is built on top of this REST API.'
version: 0.92.0 version: 0.93.0
schemes: schemes:
- http - http
basePath: /control basePath: /control
@ -12,6 +12,9 @@ tags:
- -
name: global name: global
description: 'AdGuard Home server general settings and controls' description: 'AdGuard Home server general settings and controls'
-
name: tls
description: 'AdGuard Home HTTPS/DOH/DOT settings'
- -
name: log name: log
description: 'AdGuard Home query log' description: 'AdGuard Home query log'
@ -267,6 +270,70 @@ paths:
200: 200:
description: OK description: OK
# --------------------------------------------------
# TLS server methods
# --------------------------------------------------
/tls/status:
get:
tags:
- tls
operationId: tlsStatus
summary: "Returns TLS configuration and its status"
responses:
200:
description: OK
schema:
$ref: "#/definitions/TlsConfig"
/tls/configure:
post:
tags:
- tls
operationId: tlsConfigure
summary: "Updates current TLS configuration"
consumes:
- application/json
parameters:
- in: "body"
name: "body"
description: "TLS configuration JSON"
required: true
schema:
$ref: "#/definitions/TlsConfig"
responses:
200:
description: "TLS configuration and its status"
schema:
$ref: "#/definitions/TlsConfig"
400:
description: "Invalid configuration or unavailable port"
500:
description: "Error occurred while applying configuration"
/tls/validate:
post:
tags:
- tls
operationId: tlsValidate
summary: "Checks if the current TLS configuration is valid"
consumes:
- application/json
parameters:
- in: "body"
name: "body"
description: "TLS configuration JSON"
required: true
schema:
$ref: "#/definitions/TlsConfig"
responses:
200:
description: "TLS configuration and its status"
schema:
$ref: "#/definitions/TlsConfig"
400:
description: "Invalid configuration or unavailable port"
# -------------------------------------------------- # --------------------------------------------------
# DHCP server methods # DHCP server methods
# -------------------------------------------------- # --------------------------------------------------
@ -1064,3 +1131,80 @@ definitions:
description: "Query log" description: "Query log"
items: items:
$ref: "#/definitions/QueryLogItem" $ref: "#/definitions/QueryLogItem"
TlsConfig:
type: "object"
description: "TLS configuration settings and status"
properties:
# TLS configuration
enabled:
type: "boolean"
example: "true"
description: "enabled is the encryption (DOT/DOH/HTTPS) status"
server_name:
type: "string"
example: "example.org"
description: "server_name is the hostname of your HTTPS/TLS server"
force_https:
type: "boolean"
example: "true"
description: "if true, forces HTTP->HTTPS redirect"
port_https:
type: "integer"
format: "int32"
example: 443
description: "HTTPS port. If 0, HTTPS will be disabled."
port_dns_over_tls:
type: "integer"
format: "int32"
example: 853
description: "DNS-over-TLS port. If 0, DOT will be disabled."
certificate_chain:
type: "string"
description: "Base64 string with PEM-encoded certificates chain"
private_key:
type: "string"
description: "Base64 string with PEM-encoded private key"
# Below goes validation fields
valid_cert:
type: "boolean"
example: "true"
description: "valid_cert is true if the specified certificates chain is a valid chain of X509 certificates"
valid_chain:
type: "boolean"
example: "true"
description: "valid_chain is true if the specified certificates chain is verified and issued by a known CA"
subject:
type: "string"
example: "CN=example.org"
description: "subject is the subject of the first certificate in the chain"
issuer:
type: "string"
example: "CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US"
description: "issuer is the issuer of the first certificate in the chain"
not_before:
type: "string"
example: "2019-01-31T10:47:32Z"
description: "not_before is the NotBefore field of the first certificate in the chain"
not_after:
type: "string"
example: "2019-05-01T10:47:32Z"
description: "not_after is the NotAfter field of the first certificate in the chain"
dns_names:
type: "array"
items:
type: "string"
description: "dns_names is the value of SubjectAltNames field of the first certificate in the chain"
example:
- "*.example.org"
valid_key:
type: "boolean"
example: "true"
description: "valid_key is true if the key is a valid private key"
key_type:
type: "string"
example: "RSA"
description: "key_type is either RSA or ECDSA"
warning_validation:
type: "string"
example: "You have specified an empty certificate"
description: "warning_validation is a validation warning message with the issue description"