Skip to content

Commit

Permalink
Merge pull request #3 from armbian/feature/version-check
Browse files Browse the repository at this point in the history
Feature: Version Checks, Rules
  • Loading branch information
igorpecovnik authored Jan 28, 2024
2 parents 1bbd292 + 7fc823f commit 2048c14
Show file tree
Hide file tree
Showing 19 changed files with 1,012 additions and 1,065 deletions.
11 changes: 3 additions & 8 deletions .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ steps:
environment:
CGO_ENABLED: '0'
- name: build
image: tystuyfzand/goc:latest
image: tystuyfzand/goc:1.21
volumes:
- name: build
path: /build
Expand All @@ -30,24 +30,19 @@ steps:
GOARCH: 386,amd64,arm,arm64
depends_on: [ test ]
- name: release
image: plugins/gitea-release
image: plugins/github-release
volumes:
- name: build
path: /build
settings:
api_key:
from_secret: gitea_token
base_url: https://git.meow.tf
title: release
from_secret: github_token
files:
- /build/dlrouter_*
checksum:
- md5
- sha1
- sha256
environment:
PLUGIN_API_KEY:
from_secret: gitea_token
when:
event: tag
depends_on: [ build ]
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,27 @@ servers:
longitude: -88.1995
# Example of a server with additional protocols (rsync)
# Useful for defining servers which could be used for rsync sources
# This lets us potentially add an endpoint to say "give me a server with rsync"
- server: mirrors.dotsrc.org/armbian-apt/
weight: 15
protocols:
- http
- https
- rsync
# Example of a server with rules
- server: armbian.lv.auroradev.org/apt/
rules:
# Required: field
# Value matchers: is, is_not, in, not_in
# See the RuleInput struct, as well as the ASN and
# This example excludes Google's ASN from this mirror
- field: asn.autonomous_system_number
is_not: 15169
# An example of a country blocking access to another
# For instance, Ukraine not allowing Russian ISPs in.
- field: location.country.iso_code
not_in:
- RU
````

## API
Expand Down
153 changes: 131 additions & 22 deletions check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"crypto/x509"
"errors"
"fmt"
"github.com/samber/lo"
log "github.com/sirupsen/logrus"
"io"
"net"
"net/http"
"net/url"
"path"
"runtime"
"strings"
"time"
Expand All @@ -28,14 +31,18 @@ var (
ErrCertExpired = errors.New("certificate is expired")
)

func (r *Redirector) checkHTTP(scheme string) ServerCheck {
return func(server *Server, logFields log.Fields) (bool, error) {
return r.checkHTTPScheme(server, scheme, logFields)
}
// HTTPCheck is a check for validity and redirects
type HTTPCheck struct {
config *Config
}

// Check checks a URL for validity, and checks redirects
func (h *HTTPCheck) Check(server *Server, logFields log.Fields) (bool, error) {
return h.checkHTTPScheme(server, "http", logFields)
}

// checkHTTPScheme checks a URL for validity, and checks redirects
func (r *Redirector) checkHTTPScheme(server *Server, scheme string, logFields log.Fields) (bool, error) {
// checkHTTPScheme will check if a scheme is valid and doesn't redirect
func (h *HTTPCheck) checkHTTPScheme(server *Server, scheme string, logFields log.Fields) (bool, error) {
u := &url.URL{
Scheme: scheme,
Host: server.Host,
Expand All @@ -50,7 +57,7 @@ func (r *Redirector) checkHTTPScheme(server *Server, scheme string, logFields lo
return false, err
}

res, err := r.config.checkClient.Do(req)
res, err := h.config.checkClient.Do(req)

if err != nil {
return false, err
Expand All @@ -66,18 +73,18 @@ func (r *Redirector) checkHTTPScheme(server *Server, scheme string, logFields lo

switch u.Scheme {
case "http":
res, err := r.checkRedirect(u.Scheme, location)
res, err := h.checkRedirect(u.Scheme, location)

if !res || err != nil {
// If we don't support http, we remove it from supported protocols
server.Protocols = server.Protocols.Remove("http")
server.Protocols = Remove(server.Protocols, "http")
} else {
// Otherwise, we verify https support
r.checkProtocol(server, "https")
h.checkProtocol(server, "https")
}
case "https":
// We don't want to allow downgrading, so this is an error.
return r.checkRedirect(u.Scheme, location)
return h.checkRedirect(u.Scheme, location)
}
}

Expand All @@ -89,20 +96,20 @@ func (r *Redirector) checkHTTPScheme(server *Server, scheme string, logFields lo
return false, nil
}

func (r *Redirector) checkProtocol(server *Server, scheme string) {
res, err := r.checkHTTPScheme(server, scheme, log.Fields{})
func (h *HTTPCheck) checkProtocol(server *Server, scheme string) {
res, err := h.checkHTTPScheme(server, scheme, log.Fields{})

if !res || err != nil {
return
}

if !server.Protocols.Contains(scheme) {
server.Protocols = server.Protocols.Append(scheme)
if !lo.Contains(server.Protocols, scheme) {
server.Protocols = append(server.Protocols, scheme)
}
}

// checkRedirect parses a location header response and checks the scheme
func (r *Redirector) checkRedirect(originatingScheme, locationHeader string) (bool, error) {
func (h *HTTPCheck) checkRedirect(originatingScheme, locationHeader string) (bool, error) {
newURL, err := url.Parse(locationHeader)

if err != nil {
Expand All @@ -118,8 +125,13 @@ func (r *Redirector) checkRedirect(originatingScheme, locationHeader string) (bo
return true, nil
}

// checkTLS checks tls certificates from a host, ensures they're valid, and not expired.
func (r *Redirector) checkTLS(server *Server, logFields log.Fields) (bool, error) {
// TLSCheck is a TLS certificate check
type TLSCheck struct {
config *Config
}

// Check checks tls certificates from a host, ensures they're valid, and not expired.
func (t *TLSCheck) Check(server *Server, logFields log.Fields) (bool, error) {
var host, port string
var err error

Expand All @@ -144,7 +156,7 @@ func (r *Redirector) checkTLS(server *Server, logFields log.Fields) (bool, error
}

conn, err := tls.Dial("tcp", host+":"+port, &tls.Config{
RootCAs: r.config.RootCAs,
RootCAs: t.config.RootCAs,
})

if err != nil {
Expand Down Expand Up @@ -174,7 +186,7 @@ func (r *Redirector) checkTLS(server *Server, logFields log.Fields) (bool, error
}

opts := x509.VerifyOptions{
Roots: r.config.RootCAs,
Roots: t.config.RootCAs,
Intermediates: peerPool,
CurrentTime: time.Now(),
}
Expand Down Expand Up @@ -207,8 +219,105 @@ func (r *Redirector) checkTLS(server *Server, logFields log.Fields) (bool, error
}

// If https is valid, append it
if !server.Protocols.Contains("https") {
server.Protocols = server.Protocols.Append("https")
if !lo.Contains(server.Protocols, "https") {
server.Protocols = append(server.Protocols, "https")
}

return true, nil
}

type VersionCheck struct {

Check failure on line 229 in check.go

View workflow job for this annotation

GitHub Actions / build

exported type VersionCheck should have comment or be unexported
config *Config
VersionURL string
lastVersion string
lastVersionTime time.Time
}

func (v *VersionCheck) getCurrentVersion() (string, error) {
if v.lastVersion != "" && time.Now().Before(v.lastVersionTime.Add(5*time.Minute)) {
return v.lastVersion, nil
}

req, err := http.NewRequest(http.MethodGet, v.VersionURL, nil)

req.Header.Set("User-Agent", "ArmbianRouter/1.0 (Go "+runtime.Version()+")")

if err != nil {
return "", err
}

res, err := v.config.checkClient.Do(req)

if err != nil {
return "", err
}

defer res.Body.Close()

b, err := io.ReadAll(io.LimitReader(res.Body, 128))

if err != nil {
return "", err
}

v.lastVersion = strings.TrimSpace(string(b))
v.lastVersionTime = time.Now()

return v.lastVersion, nil
}

func (v *VersionCheck) Check(server *Server, logFields log.Fields) (bool, error) {

Check failure on line 269 in check.go

View workflow job for this annotation

GitHub Actions / build

exported method VersionCheck.Check should have comment or be unexported
currentVersion, err := v.getCurrentVersion()

if err != nil {
return false, err
}

controlPath := path.Join(server.Path, ".control")

u := &url.URL{
Scheme: "https",
Host: server.Host,
Path: controlPath,
}

if !lo.Contains(server.Protocols, "https") {
u.Scheme = "http"
}

req, err := http.NewRequest(http.MethodGet, u.String(), nil)

req.Header.Set("User-Agent", "ArmbianRouter/1.0 (Go "+runtime.Version()+")")

if err != nil {
return false, err
}

res, err := v.config.checkClient.Do(req)

if err != nil {
return false, err
}

defer res.Body.Close()

if res.StatusCode != 200 {
logFields["error"] = "Control file does not exist"
return false, nil
}

b, err := io.ReadAll(io.LimitReader(res.Body, 128))

if err != nil {
return false, err
}

actualVersion := strings.TrimSpace(string(b))

if actualVersion != currentVersion {
logFields["expectedVersion"] = currentVersion
logFields["actualVersion"] = actualVersion
return false, fmt.Errorf("version mismatch, expected: %s, actual: %s", currentVersion, actualVersion)
}

return true, nil
Expand Down
Loading

0 comments on commit 2048c14

Please sign in to comment.