mirror of
https://gerrit.hackerspace.pl/hscloud
synced 2025-03-22 11:55:07 +00:00
hswaw/capacifier: rewrite it in go
This reimplements capacifier, one of the earliest just-some-flask-code-on-boston-packets services, in Go. It's a minimum reimplementation, as this service is generally deprecated - but some stuff still depends on it. So we do away with capacifier v0's bespoke rule language and just hardcode everything. It's not like any of these rules ever changed, anyway. This is not yet deployed. Change-Id: Id65ef92784a524c32ae5223cd5460736ac683116 Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1509 Reviewed-by: ironbound <ironbound@hackerspace.pl>
This commit is contained in:
parent
90cf314d1e
commit
0aa2910d00
6 changed files with 358 additions and 0 deletions
45
hswaw/capacifier/BUILD.bazel
Normal file
45
hswaw/capacifier/BUILD.bazel
Normal file
|
@ -0,0 +1,45 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_layer", "container_push")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["capacifier.go"],
|
||||
importpath = "code.hackerspace.pl/hscloud/hswaw/capacifier",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//go/mirko:go_default_library",
|
||||
"@com_github_golang_glog//:go_default_library",
|
||||
"@in_gopkg_ldap_v3//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "capacifier",
|
||||
embed = [":go_default_library"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
container_layer(
|
||||
name = "layer_bin",
|
||||
directory = "/hswaw/capacifier/",
|
||||
files = [
|
||||
":capacifier",
|
||||
],
|
||||
)
|
||||
|
||||
container_image(
|
||||
name = "runtime",
|
||||
base = "@prodimage-bionic//image",
|
||||
layers = [
|
||||
":layer_bin",
|
||||
],
|
||||
)
|
||||
|
||||
container_push(
|
||||
name = "push",
|
||||
format = "Docker",
|
||||
image = ":runtime",
|
||||
registry = "registry.k0.hswaw.net",
|
||||
repository = "q3k/capacifier",
|
||||
tag = "1680390588",
|
||||
)
|
23
hswaw/capacifier/README.md
Normal file
23
hswaw/capacifier/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
capacifier
|
||||
===
|
||||
|
||||
rewrite-in-go of code.haclerspace.pl/tomek/capacifier.
|
||||
|
||||
This is one of the oldest API services at the Warsaw hackerspace, and exists
|
||||
solely to provide a generic 'is X a member of Y' functionality. It's generally
|
||||
deprecated (instead OIDC should be used as much as possible), but it's so
|
||||
entrenched into our infra that it's difficult to fully kill.
|
||||
|
||||
While the previous implementation had a whole bespoke rule expression language,
|
||||
this implementation is stupidly simple, with all rules hardcoded.
|
||||
|
||||
Running
|
||||
---
|
||||
|
||||
Get the password for the capacifier service account from prod.
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
bazel run //hswaw/capacifier -- --ldap_bind_pw xxx
|
||||
```
|
203
hswaw/capacifier/capacifier.go
Normal file
203
hswaw/capacifier/capacifier.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
ldap "gopkg.in/ldap.v3"
|
||||
|
||||
"code.hackerspace.pl/hscloud/go/mirko"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
mu sync.Mutex
|
||||
ldap *ldap.Conn
|
||||
}
|
||||
|
||||
var reURL = regexp.MustCompile(`^/([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$`)
|
||||
|
||||
func (s *server) handle(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" {
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
fmt.Fprintf(rw, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
reqParts := reURL.FindStringSubmatch(req.URL.Path)
|
||||
if len(reqParts) != 3 {
|
||||
fmt.Fprintf(rw, "usage: GET /capability/user, eg. GET /staff/q3k")
|
||||
return
|
||||
}
|
||||
c := reqParts[1]
|
||||
u := reqParts[2]
|
||||
|
||||
res, err := s.capacify(c, u)
|
||||
l := ""
|
||||
r := ""
|
||||
switch {
|
||||
case err != nil:
|
||||
l = fmt.Sprintf("%v", err)
|
||||
r = "ERROR"
|
||||
rw.WriteHeader(500)
|
||||
case res:
|
||||
l = "yes"
|
||||
r = "YES"
|
||||
rw.WriteHeader(200)
|
||||
default:
|
||||
l = "no"
|
||||
r = "NO"
|
||||
rw.WriteHeader(401)
|
||||
}
|
||||
glog.Infof("%s: GET /%s/%s: %s", req.RemoteAddr, c, u, l)
|
||||
fmt.Fprintf(rw, "%s", r)
|
||||
}
|
||||
|
||||
func (s *server) capacify(c, u string) (bool, error) {
|
||||
switch c {
|
||||
case "xmpp":
|
||||
return s.checkLdap(u, "cn=xmpp-users,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "wiki_admin":
|
||||
return s.checkLdap(u, "cn=admin,dc=wiki,dc=hackerspace,dc=pl")
|
||||
case "twitter":
|
||||
return s.checkLdap(u, "cn=twitter,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "lulzbot_access":
|
||||
return s.checkLdap(u, "cn=lulzbot-access,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "staff":
|
||||
return s.checkLdap(u, "cn=staff,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "kasownik_access":
|
||||
return s.checkLdap(u, "cn=kasownik-access,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "starving":
|
||||
return s.checkLdap(u, "cn=starving,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "fatty":
|
||||
return s.checkLdap(u, "cn=fatty,ou=Group,dc=hackerspace,dc=pl")
|
||||
case "member":
|
||||
// Where we're going we don't need applicatives.
|
||||
res, err := s.capacify("fatty", u)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if res {
|
||||
return true, nil
|
||||
}
|
||||
return s.capacify("starving", u)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) getLdap() (*ldap.Conn, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.ldap == nil {
|
||||
lconn, err := connectLdap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.ldap = lconn
|
||||
}
|
||||
return s.ldap, nil
|
||||
}
|
||||
|
||||
func (s *server) closeLdap() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.ldap != nil {
|
||||
s.ldap.Close()
|
||||
s.ldap = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) checkLdap(u, dn string) (bool, error) {
|
||||
lconn, err := s.getLdap()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if strings.ContainsAny(u, `\#+<>,;"=`) {
|
||||
return false, nil
|
||||
}
|
||||
filter := fmt.Sprintf("(uniqueMember=uid=%s,ou=People,dc=hackerspace,dc=pl)", u)
|
||||
search := ldap.NewSearchRequest(
|
||||
dn,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
filter, []string{"dn", "cn"}, nil,
|
||||
)
|
||||
sr, err := lconn.Search(search)
|
||||
if err != nil {
|
||||
s.closeLdap()
|
||||
return false, fmt.Errorf("search failed: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range sr.Entries {
|
||||
if entry.DN == dn {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Set("logtostderr", "true")
|
||||
}
|
||||
|
||||
var (
|
||||
flagLDAPServer string
|
||||
flagLDAPBindDN string
|
||||
flagLDAPBindPW string
|
||||
flagListen string
|
||||
)
|
||||
|
||||
func connectLdap() (*ldap.Conn, error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
lconn, err := ldap.DialTLS("tcp", flagLDAPServer, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ldap.DialTLS: %v", err)
|
||||
}
|
||||
|
||||
if err := lconn.Bind(flagLDAPBindDN, flagLDAPBindPW); err != nil {
|
||||
lconn.Close()
|
||||
return nil, fmt.Errorf("ldap.Bind: %v", err)
|
||||
}
|
||||
return lconn, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&flagListen, "api_listen", ":2137", "Address to listen on for API requests")
|
||||
flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "LDAP server address")
|
||||
flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "cn=capacifier,ou=Services,dc=hackerspace,dc=pl", "LDAP bind DN")
|
||||
flag.StringVar(&flagLDAPBindPW, "ldap_bind_pw", "", "LDAP bind password")
|
||||
flag.Parse()
|
||||
|
||||
if flagLDAPBindPW == "" {
|
||||
glog.Exitf("-ldap_bind_pw must be set")
|
||||
}
|
||||
|
||||
m := mirko.New()
|
||||
if err := m.Listen(); err != nil {
|
||||
glog.Exitf("Listen(): %v", err)
|
||||
}
|
||||
|
||||
s := &server{}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", s.handle)
|
||||
|
||||
go func() {
|
||||
glog.Infof("API Listening on %s", flagListen)
|
||||
if err := http.ListenAndServe(flagListen, mux); err != nil {
|
||||
glog.Exitf("API Listen failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := m.Serve(); err != nil {
|
||||
glog.Exitf("Serve(): %v", err)
|
||||
}
|
||||
|
||||
<-m.Done()
|
||||
}
|
41
hswaw/kube/capacifier.libsonnet
Normal file
41
hswaw/kube/capacifier.libsonnet
Normal file
|
@ -0,0 +1,41 @@
|
|||
local mirko = import "../../kube/mirko.libsonnet";
|
||||
local kube = import "../../kube/kube.libsonnet";
|
||||
|
||||
{
|
||||
cfg:: {
|
||||
ldapBindPassword: error "ldapBindPassword must be set!",
|
||||
image: "registry.k0.hswaw.net/q3k/capacifier:1680390588",
|
||||
fqdn: "capacifier.hackerspace.pl",
|
||||
},
|
||||
|
||||
component(cfg, env):: mirko.Component(env, "capacifier") {
|
||||
local capacifier = self,
|
||||
cfg+: {
|
||||
image: cfg.image,
|
||||
container: capacifier.GoContainer("main", "/hswaw/capacifier/capacifier") {
|
||||
env_: {
|
||||
BIND_PW: kube.SecretKeyRef(capacifier.secret, "bindPW"),
|
||||
},
|
||||
command+: [
|
||||
"-listen", "0.0.0.0:5000",
|
||||
"-ldap_bind_pw", "$(BIND_PW)",
|
||||
],
|
||||
},
|
||||
ports+: {
|
||||
publicHTTP: {
|
||||
api: {
|
||||
port: 5000,
|
||||
dns: cfg.fqdn,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
secret: kube.Secret("capacifier") {
|
||||
metadata+: capacifier.metadata,
|
||||
data_: {
|
||||
bindPW: cfg.ldapBindPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -8,6 +8,7 @@ local frab = import "frab.libsonnet";
|
|||
local pretalx = import "pretalx.libsonnet";
|
||||
local cebulacamp = import "cebulacamp.libsonnet";
|
||||
local site = import "site.libsonnet";
|
||||
local capacifier = import "capacifier.libsonnet";
|
||||
|
||||
{
|
||||
hswaw(name):: mirko.Environment(name) {
|
||||
|
@ -22,6 +23,7 @@ local site = import "site.libsonnet";
|
|||
pretalx: pretalx.cfg,
|
||||
cebulacamp: cebulacamp.cfg,
|
||||
site: site.cfg,
|
||||
capacifier: capacifier.cfg,
|
||||
},
|
||||
|
||||
components: {
|
||||
|
@ -33,6 +35,7 @@ local site = import "site.libsonnet";
|
|||
pretalx: pretalx.component(cfg.pretalx, env),
|
||||
cebulacamp: cebulacamp.component(cfg.cebulacamp, env),
|
||||
site: site.component(cfg.site, env),
|
||||
capacifier: capacifier.component(cfg.capacifier, env),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -75,6 +78,9 @@ local site = import "site.libsonnet";
|
|||
site+: {
|
||||
webFQDN: "new.hackerspace.pl",
|
||||
},
|
||||
capacifier+: {
|
||||
ldapBindPassword: std.base64(std.split(importstr "secrets/plain/prod-capacifier-password", "\n")[0]),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
40
hswaw/kube/secrets/cipher/prod-capacifier-password
Normal file
40
hswaw/kube/secrets/cipher/prod-capacifier-password
Normal file
|
@ -0,0 +1,40 @@
|
|||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
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
|
||||
=SBGB
|
||||
-----END PGP MESSAGE-----
|
Loading…
Add table
Reference in a new issue