split checker from token-validator

This commit is contained in:
nemunaire 2020-03-27 14:57:14 +01:00
parent 685dc0b0ea
commit 0c661f36f6
20 changed files with 634 additions and 748 deletions

View File

@ -9,6 +9,8 @@ import (
"github.com/miekg/dns"
"github.com/sparrc/go-ping"
"git.nemunai.re/lectures/adlin/libadlin"
)
// ICMP
@ -29,11 +31,6 @@ func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) {
return
}
func (s *Student) onPong(state bool) (err error) {
_, err = DBExec("INSERT INTO student_pong (id_student, time, state) VALUES (?, ?, ?)", s.Id, time.Now(), state)
return
}
// PORT 53
func check_dns(domain, ip string) (err error) {
@ -75,7 +72,7 @@ func check_https(domain, ip string) (err error) {
// Main
func studentsChecker() {
students, err := getStudents()
students, err := adlin.GetStudents()
if err != nil {
log.Println("Unable to check students:", err)
return
@ -85,9 +82,9 @@ func studentsChecker() {
time.Sleep(250 * time.Millisecond)
// Check ping
std := s
stdIP := studentIP(std.Id).String() + "1"
stdIP := adlin.StudentIP(std.Id).String() + "1"
go check_ping(stdIP, func(pkt *ping.Packet) {
std.onPong(true)
std.OnPong(true)
// Check HTTP
if err := check_http(stdIP); err == nil {
@ -99,7 +96,7 @@ func studentsChecker() {
}
// Check HTTPs
if err := check_https(std.myAssociatedDomain(), stdIP); err == nil {
if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil {
if _, err := std.UnlockNewChallenge(101, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(101, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())

View File

@ -1,4 +1,4 @@
package main
package adlin
import (
"database/sql"

27
libadlin/domain.go Normal file
View File

@ -0,0 +1,27 @@
package adlin
import (
"fmt"
"strings"
)
const (
AssociatedDomainSuffix = "adlin2021.p0m.fr."
DelegatedDomainSuffix = "srs.p0m.fr."
)
func (student Student) MyDelegatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix)
}
func (student Student) MyAssociatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix)
}
func (student Student) GetAssociatedDomains() (ds []string) {
studentDomain := student.MyAssociatedDomain()
ds = append(ds, studentDomain)
return
}

36
libadlin/ping.go Normal file
View File

@ -0,0 +1,36 @@
package adlin
import (
"time"
)
type Pong struct {
Date time.Time
State bool
}
func (s Student) LastPongs() (pongs []Pong, err error) {
if rows, errr := DBQuery("SELECT time, state FROM student_pong WHERE id_student = ? ORDER BY time DESC", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var p Pong
if err = rows.Scan(&p.Date, &p.State); err != nil {
return
}
pongs = append(pongs, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s *Student) OnPong(state bool) (err error) {
_, err = DBExec("INSERT INTO student_pong (id_student, time, state) VALUES (?, ?, ?)", s.Id, time.Now(), state)
return
}

3
libadlin/secret.go Normal file
View File

@ -0,0 +1,3 @@
package adlin
var SharedSecret string

View File

@ -1,4 +1,4 @@
package main
package adlin
import (
"crypto/rand"
@ -11,7 +11,7 @@ type Session struct {
Time time.Time `json:"time"`
}
func getSession(id []byte) (s Session, err error) {
func GetSession(id []byte) (s Session, err error) {
err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time)
return
}

135
libadlin/ssh.go Normal file
View File

@ -0,0 +1,135 @@
package adlin
import (
"bytes"
"errors"
"os/exec"
"strconv"
"strings"
"time"
)
type StudentKey struct {
Id int64 `json:"id"`
IdStudent int64 `json:"id_student"`
Key string `json:"key"`
Time time.Time `json:"time"`
}
func GetStudentKeys() (keys []StudentKey, err error) {
if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var k StudentKey
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) GetKeys() (keys []StudentKey, err error) {
if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_student = ?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var k StudentKey
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getStudentKey(id int) (k StudentKey, err error) {
err = DBQueryRow("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time)
return
}
func (s Student) NewKey(key string) (k StudentKey, err error) {
// Check key before importing it
cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
cmd.Stdin = strings.NewReader(key)
var stdoutStderr []byte
stdoutStderr, err = cmd.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = errors.New(string(stdoutStderr))
}
return
}
chunks := bytes.Fields(stdoutStderr)
keytype := string(chunks[len(chunks)-1])
minkeysize := 2048
if keytype == "(ED25519)" || keytype == "(ECDSA)" {
minkeysize = 256
}
var bits int
if bits, err = strconv.Atoi(string(chunks[0])); err != nil {
return
} else if bits < minkeysize {
err = errors.New("Keysize too small")
return
}
// Sanitize the given key
keyf := strings.Fields(key)
if len(keyf) < 2 {
err = errors.New("Unexpected key file, this should never happen")
return
}
key = keyf[0] + " " + keyf[1]
if res, err := DBExec("INSERT INTO student_keys (id_student, sshkey, time) VALUES (?, ?, ?)", s.Id, key, time.Now()); err != nil {
return StudentKey{}, err
} else if kid, err := res.LastInsertId(); err != nil {
return StudentKey{}, err
} else {
s.UnlockNewChallenge(9, "")
return StudentKey{kid, s.Id, key, time.Now()}, nil
}
}
func (k StudentKey) GetStudent() (Student, error) {
return GetStudent(int(k.IdStudent))
}
func (k StudentKey) Update() (int64, error) {
if res, err := DBExec("UPDATE student_keys SET id_student = ?, sshkey = ?, time = ? WHERE id_key = ?", k.IdStudent, k.Key, k.Time, k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func (k StudentKey) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}

177
libadlin/students.go Normal file
View File

@ -0,0 +1,177 @@
package adlin
import (
"crypto/hmac"
"crypto/sha512"
"time"
)
type Student struct {
Id int64 `json:"id"`
Login string `json:"login"`
Time *time.Time `json:"time"`
IP *string `json:"ip"`
MAC *string `json:"mac"`
}
func GetStudents() (students []Student, err error) {
if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var s Student
if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil {
return
}
students = append(students, s)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func GetStudent(id int) (s Student, err error) {
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC)
return
}
func GetStudentByLogin(login string) (s Student, err error) {
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC)
return
}
func StudentExists(login string) bool {
var z int
err := DBQueryRow("SELECT 1 FROM students WHERE login=?", login).Scan(&z)
return err == nil && z == 1
}
func NewStudent(login string) (Student, error) {
t := time.Now()
if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil {
return Student{}, err
} else if sid, err := res.LastInsertId(); err != nil {
return Student{}, err
} else {
return Student{sid, login, &t, nil, nil}, nil
}
}
func (s Student) GetPKey() []byte {
return hmac.New(sha512.New512_224, []byte(SharedSecret)).Sum([]byte(s.Login))
}
func (s Student) Update() (int64, error) {
if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func (s Student) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func ClearStudents() (int64, error) {
if res, err := DBExec("DELETE FROM students"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
type UnlockedChallenge struct {
Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"`
Value interface{} `json:"value,omitempty"`
}
func (s Student) GetStates() (ucs []UnlockedChallenge, err error) {
if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u UnlockedChallenge
u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) {
if rows, errr := DBQuery("SELECT id_st, challenge, MIN(time), value FROM student_challenges WHERE id_student = ? GROUP BY challenge, id_student", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u UnlockedChallenge
u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) {
if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil {
return UnlockedChallenge{}, err
} else if utid, err := res.LastInsertId(); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err
}
}
func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) {
if _, err := DBExec("UPDATE student_challenges SET time = ?, value = ? WHERE id_student = ? AND challenge = ?", time.Now(), value, s.Id, challenge); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err
}
}
func (s Student) RegisterAccess(ip, mac string) error {
if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil {
return err
} else if _, err := res.LastInsertId(); err != nil {
return err
} else {
return err
}
}

View File

@ -8,6 +8,8 @@ import (
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
var AuthFunc = checkAuth
@ -20,17 +22,17 @@ func init() {
router.POST("/api/auth/logout", apiRawHandler(logout))
}
func validateAuthToken(s Student, _ httprouter.Params, _ []byte) (interface{}, error) {
func validateAuthToken(s adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) {
return s, nil
}
func logout(w http.ResponseWriter, ps httprouter.Params, body []byte) (interface{}, error) {
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: "",
Path: baseURL,
Expires: time.Unix(0,0),
Secure: true,
Name: "auth",
Value: "",
Path: baseURL + "/",
Expires: time.Unix(0, 0),
Secure: true,
HttpOnly: true,
})
@ -42,18 +44,18 @@ type loginForm struct {
Password string
}
func completeAuth(w http.ResponseWriter, username string, session *Session) (err error) {
var std Student
if !studentExists(username) {
if std, err = NewStudent(username); err != nil {
func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) {
var std adlin.Student
if !adlin.StudentExists(username) {
if std, err = adlin.NewStudent(username); err != nil {
return err
}
} else if std, err = getStudentByLogin(username); err != nil {
} else if std, err = adlin.GetStudentByLogin(username); err != nil {
return err
}
if session == nil {
var s Session
var s adlin.Session
s, err = std.NewSession()
session = &s
} else {
@ -65,11 +67,11 @@ func completeAuth(w http.ResponseWriter, username string, session *Session) (err
}
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: base64.StdEncoding.EncodeToString(session.Id),
Path: baseURL,
Expires: time.Now().Add(30 * 24 * time.Hour),
Secure: true,
Name: "auth",
Value: base64.StdEncoding.EncodeToString(session.Id),
Path: baseURL + "/",
Expires: time.Now().Add(30 * 24 * time.Hour),
Secure: true,
HttpOnly: true,
})
@ -91,7 +93,7 @@ func checkAuth(w http.ResponseWriter, _ httprouter.Params, body []byte) (interfa
return nil, err
}
if r, err := http.NewRequest("GET", "https://fic.srs.epita.fr/2021/", nil); err != nil {
if r, err := http.NewRequest("GET", "https://owncloud.srs.epita.fr/remote.php/dav/", nil); err != nil {
return nil, err
} else {
r.SetBasicAuth(lf.Username, lf.Password)

View File

@ -12,11 +12,13 @@ import (
"github.com/coreos/go-oidc"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
var (
oidcClientID = ""
oidcSecret = ""
oidcSecret = ""
oauth2Config oauth2.Config
oidcVerifier *oidc.IDTokenVerifier
)
@ -49,7 +51,7 @@ func initializeOIDC() {
}
oidcConfig := oidc.Config{
ClientID: oidcClientID,
ClientID: oidcClientID,
}
oidcVerifier = provider.Verifier(&oidcConfig)
}
@ -57,7 +59,7 @@ func initializeOIDC() {
}
func redirectOIDC_CRI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
session, err := NewSession()
session, err := adlin.NewSession()
if err != nil {
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusInternalServerError)
} else {
@ -72,7 +74,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par
return
}
session, err := getSession(idsession)
session, err := adlin.GetSession(idsession)
if err != nil {
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest)
return

View File

@ -15,27 +15,29 @@ import (
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
const IPgwDMZ = "172.23.200.1"
type Challenge struct {
Accessible []func(*Student, *http.Request) error
Check func(*Student, *givenToken, int) error
Accessible []func(*adlin.Student, *http.Request) error
Check func(*adlin.Student, *givenToken, int) error
}
/* Restrictions */
func noAccessRestriction(*Student, *http.Request) error {
func noAccessRestriction(*adlin.Student, *http.Request) error {
return nil
}
func noAccess(*Student, *http.Request) error {
func noAccess(*adlin.Student, *http.Request) error {
return errors.New("This challenge cannot be accessed this way. ")
}
func accessFrom(ip string) func(_ *Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error {
func accessFrom(ip string) func(_ *adlin.Student, r *http.Request) error {
return func(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-By") != ip {
return errors.New("This challenge is not accessible this way.")
}
@ -43,8 +45,8 @@ func accessFrom(ip string) func(_ *Student, r *http.Request) error {
}
}
func notAccessFrom(ip string) func(_ *Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error {
func notAccessFrom(ip string) func(_ *adlin.Student, r *http.Request) error {
return func(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-By") == ip {
return errors.New("This challenge is not accessible this way.")
}
@ -52,8 +54,8 @@ func notAccessFrom(ip string) func(_ *Student, r *http.Request) error {
}
}
func maxProxy(nb int) func(_ *Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error {
func maxProxy(nb int) func(_ *adlin.Student, r *http.Request) error {
return func(_ *adlin.Student, r *http.Request) error {
if len(strings.Split(r.Header.Get("X-Forwarded-For"), ",")) > nb {
return errors.New("This challenge is not accessible this way.")
}
@ -61,50 +63,49 @@ func maxProxy(nb int) func(_ *Student, r *http.Request) error {
}
}
func sslOnly(_ *Student, r *http.Request) error {
func sslOnly(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-Proto") != "https" {
return errors.New("This challenge should be performed over TLS.")
}
return nil
}
/* Challenges */
func challenge42(s *Student, t *givenToken, chid int) error {
func challenge42(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte("42")); err != nil {
return err
} else if ! hmac.Equal(expectedToken, t.token) {
} else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.")
} else {
return nil
}
}
func challengeDNS(s *Student, t *givenToken, chid int) error {
func challengeDNS(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte("8dde678132d6c558fc6adaeb9f1d53bf6ec7b876308cf98c48604caa9138523c1ce58b672c87c7e7d9b7248b81804d3940dbf20bf263eeb683244f7c1143712d")); err != nil {
return err
} else if ! hmac.Equal(expectedToken, t.token) {
} else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.")
}
return nil
}
func challengeTime(s *Student, t *givenToken, chid int) error {
func challengeTime(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte(t.Data[0])); err != nil {
return err
} else if ! hmac.Equal(expectedToken, t.token) {
} else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.")
} else if t, err := strconv.ParseInt(t.Data[0], 10, 64); err != nil {
return err
} else {
var rt time.Time
if t > 3000000000 {
rt = time.Unix(t / 1000000000, t % 1000000000)
rt = time.Unix(t/1000000000, t%1000000000)
} else {
rt = time.Unix(t, 0)
}
@ -117,7 +118,7 @@ func challengeTime(s *Student, t *givenToken, chid int) error {
}
}
func challengePing(s *Student, t *givenToken, chid int) error {
func challengePing(s *adlin.Student, t *givenToken, chid int) error {
var expected []byte
switch s.Id % 5 {
case 1:
@ -164,7 +165,7 @@ func challengePing(s *Student, t *givenToken, chid int) error {
}
}
func challengeDisk(s *Student, t *givenToken, chid int) error {
func challengeDisk(s *adlin.Student, t *givenToken, chid int) error {
pkey := fmt.Sprintf("%x", s.GetPKey())
n1, err := strconv.Atoi(t.Data[0][0:2])
@ -182,19 +183,19 @@ func challengeDisk(s *Student, t *givenToken, chid int) error {
}
if n1+n2 > len(pkey) {
n2 = len(pkey)-n1
n2 = len(pkey) - n1
}
expectedToken := sha512.Sum512([]byte(pkey[n1:n1+n2]))
expectedToken := sha512.Sum512([]byte(pkey[n1 : n1+n2]))
if ! hmac.Equal(expectedToken[:], sum) {
if !hmac.Equal(expectedToken[:], sum) {
return errors.New("This is not the expected token.")
} else {
return nil
}
}
func challengeEMail(s *Student, t *givenToken, chid int) error {
func challengeEMail(s *adlin.Student, t *givenToken, chid int) error {
return errors.New("This is not the expected token.")
}
@ -204,61 +205,61 @@ func init() {
challenges = []Challenge{
/* Challenge 1 : 42 */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
Check: challenge42,
Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challenge42,
},
/* Challenge 2 : 42 from DMZ */
Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ)},
Check: challenge42,
Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ)},
Check: challenge42,
},
/* Challenge 3 : ssl (+ ntp) */
Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeTime,
Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeTime,
},
/* Challenge 4 : DNS TXT */
Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeDNS,
Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeDNS,
},
/* Challenge 5 : time net */
Challenge{
Accessible: []func(*Student, *http.Request) error{maxProxy(1)},
Check: challengeTime,
Accessible: []func(*adlin.Student, *http.Request) error{maxProxy(1)},
Check: challengeTime,
},
/* Bonus 0 : toctoc (read in source code) */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
Check: challenge42,
Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challenge42,
},
/* Bonus 1 : echo request */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
Check: challengePing,
Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengePing,
},
/* Bonus 2 : disk */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
Check: challengeDisk,
Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengeDisk,
},
/* Bonus 3 : mail */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction},
Check: challengeEMail,
Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengeEMail,
},
/* Last : SSH key, see ssh.go:156 in NewKey function */
Challenge{
Accessible: []func(*Student, *http.Request) error{noAccess},
Accessible: []func(*adlin.Student, *http.Request) error{noAccess},
},
}
@ -272,9 +273,9 @@ func init() {
}
type givenToken struct {
Login string `json:"login"`
Challenge int `json:"challenge"`
Token string `json:"token"`
Login string `json:"login"`
Challenge int `json:"challenge"`
Token string `json:"token"`
token []byte
Data []string `json:"data"`
}
@ -293,7 +294,7 @@ func accessibleChallenge(r *http.Request, ps httprouter.Params, _ []byte) (inter
} else if chid == 0 || chid >= len(challenges) {
return nil, errors.New("This challenge doesn't exist")
} else {
for _, a := range challenges[chid - 1].Accessible {
for _, a := range challenges[chid-1].Accessible {
if err := a(nil, r); err != nil {
return nil, err
}
@ -319,13 +320,13 @@ func receiveChallenge(r *http.Request, ps httprouter.Params, body []byte) (inter
return nil, errors.New("This is not the expected token.")
}
var std Student
var std adlin.Student
if stdid, err := strconv.Atoi(gt.Login); err == nil {
if std, err = getStudent(stdid); err != nil {
if std, err = adlin.GetStudent(stdid); err != nil {
return nil, err
}
} else if std, err = getStudentByLogin(gt.Login); err != nil {
} else if std, err = adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err
}
@ -355,21 +356,21 @@ func receiveToken(r *http.Request, body []byte, chid int) (interface{}, error) {
chid = gt.Challenge
}
if chid <= 0 || chid - 1 > len(challenges) {
if chid <= 0 || chid-1 > len(challenges) {
return nil, errors.New("This challenge doesn't exist")
}
// Is the challenge accessible?
for _, a := range challenges[chid - 1].Accessible {
for _, a := range challenges[chid-1].Accessible {
if err := a(nil, r); err != nil {
return nil, err
}
}
if std, err := getStudentByLogin(gt.Login); err != nil {
if std, err := adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err
} else {
if err := challenges[chid - 1].Check(&std, &gt, chid); err != nil {
if err := challenges[chid-1].Check(&std, &gt, chid); err != nil {
log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err)
return nil, err
}

View File

@ -11,104 +11,104 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/miekg/dns"
"git.nemunai.re/lectures/adlin/libadlin"
)
const (
AssociatedDomainSuffix = "adlin2021.p0m.fr."
DelegatedDomainSuffix = "srs.p0m.fr."
ControlSocket = "[2a01:e0a:2b:2250::b]:53"
ControlSocket = "[2a01:e0a:2b:2250::b]:53"
)
var tsigSecret = map[string]string{"ddns.": "so6ZGir4GPAqINNh9U5c3A=="}
func init() {
router.GET("/api/adomains/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.GET("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.GetAssociatedDomains(), nil
}))
router.POST("/api/adomains/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return true, student.AddAssociatedDomains()
router.POST("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return true, AddAssociatedDomains(student)
}))
router.GET("/api/adomains/:dn", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.GetAssociatedDomain(ps.ByName("dn"))
router.GET("/api/adomains/:dn", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return GetAssociatedDomain(student, ps.ByName("dn"))
}))
router.GET("/api/ddomains/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.GET("/api/ddomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return []string{student.MyDelegatedDomain()}, nil
}))
router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.getRRDelegatedDomain(ps.ByName("dn"), "")
router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getRRDelegatedDomain(student, ps.ByName("dn"), "")
}))
router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.getRRDelegatedDomain(ps.ByName("dn"), "NS")
router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getRRDelegatedDomain(student, ps.ByName("dn"), "NS")
}))
router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.AddNSDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
return true, AddNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
}))
router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.UpdateNSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, ""))
return true, UpdateNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, ""))
}))
router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "NS", strings.Join(ue.Values, " "))
return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "NS", strings.Join(ue.Values, " "))
}))
router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.getRRDelegatedDomain(ps.ByName("dn"), "AAAA")
router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA")
}))
router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.AddGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
return true, AddGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
}))
router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
}))
router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
}))
router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " "))
return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " "))
}))
router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.getRRDelegatedDomain(ps.ByName("dn"), "DS")
router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getRRDelegatedDomain(student, ps.ByName("dn"), "DS")
}))
router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.AddDSDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
return true, AddDSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
}))
router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var ue Entry
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "DS", strings.Join(ue.Values, " "))
return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "DS", strings.Join(ue.Values, " "))
}))
}
@ -160,19 +160,7 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) {
return
}
func (student Student) myAssociatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix)
}
func (student Student) GetAssociatedDomains() (ds []string) {
studentDomain := student.myAssociatedDomain()
ds = append(ds, studentDomain)
return
}
func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err error) {
domains := student.GetAssociatedDomains()
found := false
for _, d := range domains {
@ -185,7 +173,7 @@ func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn))
}
if entries, errr := parseZoneRead(AssociatedDomainSuffix, dn); err != nil {
if entries, errr := parseZoneRead(adlin.AssociatedDomainSuffix, dn); err != nil {
return nil, errr
} else {
for _, e := range entries {
@ -198,19 +186,19 @@ func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
return
}
func (student Student) AddAssociatedDomains() (err error) {
func AddAssociatedDomains(student adlin.Student) (err error) {
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrAd := new(dns.A)
rrAd.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
rrAd.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
m1.Remove([]dns.RR{rrAd})
rrAAAAd := new(dns.AAAA)
rrAAAAd.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0}
rrAAAAd.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0}
m1.Remove([]dns.RR{rrAAAAd})
c := new(dns.Client)
@ -226,16 +214,16 @@ func (student Student) AddAssociatedDomains() (err error) {
m2.Id = dns.Id()
m2.Opcode = dns.OpcodeUpdate
m2.Question = make([]dns.Question, 1)
m2.Question[0] = dns.Question{AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrA := new(dns.A)
rrA.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}
rrA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}
rrA.A = net.IPv4(82, 64, 31, 248)
m2.Insert([]dns.RR{rrA})
rrAAAA := new(dns.AAAA)
rrAAAA.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}
rrAAAA.AAAA = studentIP(student.Id)
rrAAAA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}
rrAAAA.AAAA = adlin.StudentIP(student.Id)
rrAAAA.AAAA[15] = 1
m2.Insert([]dns.RR{rrAAAA})
@ -247,11 +235,7 @@ func (student Student) AddAssociatedDomains() (err error) {
return
}
func (student Student) MyDelegatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix)
}
func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry, err error) {
func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []Entry, err error) {
domains := []string{student.MyDelegatedDomain()}
found := false
for _, d := range domains {
@ -264,7 +248,7 @@ func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry,
err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn))
}
if entries, errr := parseZoneRead(DelegatedDomainSuffix, dn); err != nil {
if entries, errr := parseZoneRead(adlin.DelegatedDomainSuffix, dn); err != nil {
return nil, errr
} else {
for _, e := range entries {
@ -277,13 +261,13 @@ func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry,
return
}
func (student Student) AddNSDelegatedDomain(dn string, ttl uint32, ns string) (err error) {
func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns string) (err error) {
for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrNS := new(dns.NS)
rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl}
@ -300,13 +284,13 @@ func (student Student) AddNSDelegatedDomain(dn string, ttl uint32, ns string) (e
return
}
func (student Student) UpdateNSDelegatedDomain(dn string, ttl uint32, oldns string, ns string) (err error) {
func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) {
for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrOldNS := new(dns.NS)
rrOldNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET}
@ -328,7 +312,7 @@ func (student Student) UpdateNSDelegatedDomain(dn string, ttl uint32, oldns stri
return
}
func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint32, aaaa string) (err error) {
func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa string) (err error) {
domains := []string{student.MyDelegatedDomain()}
found := false
for _, d := range domains {
@ -346,7 +330,7 @@ func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint32, aaaa string
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var rr dns.RR
rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa))
@ -364,7 +348,7 @@ func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint32, aaaa string
return
}
func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint32, oldaaaa string, aaaa string) (err error) {
func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) {
domains := []string{student.MyDelegatedDomain()}
found := false
for _, d := range domains {
@ -382,7 +366,7 @@ func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint32, oldaaaa
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var rr dns.RR
@ -406,7 +390,7 @@ func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint32, oldaaaa
return
}
func (student Student) AddDSDelegatedDomain(dn string, ttl uint32, rdata string) (err error) {
func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata string) (err error) {
domains := []string{student.MyDelegatedDomain()}
found := false
for _, d := range domains {
@ -438,7 +422,7 @@ func (student Student) AddDSDelegatedDomain(dn string, ttl uint32, rdata string)
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var ds *dns.DS
ds = dnskey.ToDS(dns.SHA256)
@ -456,7 +440,7 @@ func (student Student) AddDSDelegatedDomain(dn string, ttl uint32, rdata string)
return
}
func (student Student) DeleteRRDelegatedDomain(dn string, rr string, values ...string) (err error) {
func DeleteRRDelegatedDomain(student adlin.Student, dn string, rr string, values ...string) (err error) {
domains := []string{student.MyDelegatedDomain()}
found := false
for _, d := range domains {
@ -474,7 +458,7 @@ func (student Student) DeleteRRDelegatedDomain(dn string, rr string, values ...s
m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", dn, rr, strings.Join(values, " ")))
if errr != nil {

View File

@ -2,6 +2,8 @@ package main
import (
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
func init() {
@ -9,7 +11,7 @@ func init() {
}
func computeGrades(_ httprouter.Params, _ []byte) (interface{}, error) {
if stds, err := getStudents(); err != nil {
if stds, err := adlin.GetStudents(); err != nil {
return nil, err
} else {
res := map[string]map[string]float32{}
@ -20,7 +22,7 @@ func computeGrades(_ httprouter.Params, _ []byte) (interface{}, error) {
"TP2": 0,
}
if states, err := std.getStatesByChallenge(); err != nil {
if states, err := std.GetStatesByChallenge(); err != nil {
return nil, err
} else {
for _, st := range states {

View File

@ -14,6 +14,8 @@ import (
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
var router = httprouter.New()
@ -26,8 +28,8 @@ type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params)) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
expectedMAC := hmac.New(sha512.New, []byte(sharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10)))
previousMAC := hmac.New(sha512.New, []byte(sharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1)))
expectedMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10)))
previousMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1)))
if aauth, err := base64.StdEncoding.DecodeString(r.Header.Get("X-ADLIN-Authentication")); err != nil {
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusUnauthorized)
@ -39,7 +41,7 @@ func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httproute
}
}
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr
@ -49,17 +51,17 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
w.Header().Set("Content-Type", "application/json")
// Read Authorization header
var student *Student = nil
var student *adlin.Student = nil
if cookie, err := r.Cookie("auth"); err == nil {
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable)
return
} else if session, err := getSession(sessionid); err != nil {
} else if session, err := adlin.GetSession(sessionid); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return
} else if session.IdStudent == nil {
student = nil
} else if std, err := getStudent(int(*session.IdStudent)); err != nil {
} else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return
} else {
@ -132,41 +134,41 @@ func responseHandler(f func(*http.Request, httprouter.Params, []byte) (interface
}
}
func challengeHandler(f func (*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
func challengeHandler(f func(*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, 0)
}
}
func definedChallengeHandler(f func (*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
func definedChallengeHandler(f func(*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, chid)
}
}
func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(func (w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) {
responseHandler(func (_ *http.Request, ps httprouter.Params, b []byte)(interface{}, error) {
func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) {
responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
return f(w, ps, b)
})(w, r, ps, b)
}, access...)
}
func apiHandler(f DispatchFunction, access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func (_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...)
func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...)
}
func apiAuthHandler(f func(Student, httprouter.Params, []byte) (interface{}, error), access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func (r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
if cookie, err := r.Cookie("auth"); err != nil {
return nil, errors.New("Authorization required")
} else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
return nil, err
} else if session, err := getSession(sessionid); err != nil {
} else if session, err := adlin.GetSession(sessionid); err != nil {
return nil, err
} else if session.IdStudent == nil {
return nil, errors.New("Authorization required")
} else if std, err := getStudent(int(*session.IdStudent)); err != nil {
} else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
return nil, err
} else {
return f(std, ps, b)
@ -174,15 +176,15 @@ func apiAuthHandler(f func(Student, httprouter.Params, []byte) (interface{}, err
}), access...)
}
func studentHandler(f func(Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
func studentHandler(f func(adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) {
return func(ps httprouter.Params, body []byte) (interface{}, error) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := getStudentByLogin(ps.ByName("sid")); err != nil {
if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {
return nil, err
} else {
return f(student, body)
}
} else if student, err := getStudent(sid); err != nil {
} else if student, err := adlin.GetStudent(sid); err != nil {
return nil, err
} else {
return f(student, body)

View File

@ -4,23 +4,25 @@ import (
"fmt"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
func init() {
router.GET("/api/ips", apiHandler(showIPs))
router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student Student, body []byte) (interface{}, error) {
router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) {
return getStudentIPs(student), nil
})))
}
func (s Student) IPSuffix() int64 {
return s.Id * 5 + 10
func IPSuffix(s adlin.Student) int64 {
return s.Id*4 + 10
}
func showIPs(_ httprouter.Params, body []byte) (interface{}, error) {
r := make(map[string]map[string]string)
students, err := getStudents()
students, err := adlin.GetStudents()
if err != nil {
return nil, err
}
@ -40,13 +42,14 @@ func showIPs(_ httprouter.Params, body []byte) (interface{}, error) {
return r, nil
}
func getStudentIPs(student Student) (r map[string]string) {
func getStudentIPs(student adlin.Student) (r map[string]string) {
r = make(map[string]string)
r["vlan0"] = fmt.Sprintf("172.23.0.%d", student.IPSuffix())
r["vlan7"] = fmt.Sprintf("172.23.142.%d", student.IPSuffix())
r["wg"] = studentIP(student.Id).String()
r["adn"] = student.myAssociatedDomain()
r["vlan0"] = fmt.Sprintf("172.23.0.%d", IPSuffix(student))
r["wg0"] = fmt.Sprintf("172.17.0.%d", IPSuffix(student))
r["vlan7"] = fmt.Sprintf("172.23.142.%d", IPSuffix(student))
r["wg"] = adlin.StudentIP(student.Id).String()
r["adn"] = student.MyAssociatedDomain()
r["ddn"] = student.MyDelegatedDomain()
return

View File

@ -12,11 +12,11 @@ import (
"path"
"strings"
"syscall"
"time"
"git.nemunai.re/lectures/adlin/libadlin"
)
var baseURL string = "/"
var sharedSecret string
type ResponseWriterPrefix struct {
real http.ResponseWriter
@ -60,9 +60,9 @@ func StripPrefix(prefix string, h http.Handler) http.Handler {
func main() {
var bind = flag.String("bind", ":8081", "Bind port/socket")
var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server")
var dsn = flag.String("dsn", adlin.DSNGenerator(), "DSN to connect to the MySQL server")
flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL")
flag.StringVar(&sharedSecret, "sharedsecret", "adelina", "secret used to communicate with remote validator")
flag.StringVar(&adlin.SharedSecret, "sharedsecret", "adelina", "secret used to communicate with remote validator")
flag.StringVar(&AuthorizedKeysLocation, "authorizedkeyslocation", AuthorizedKeysLocation, "File for allowing user to SSH to the machine")
flag.StringVar(&SshPiperLocation, "sshPiperLocation", SshPiperLocation, "Directory containing directories for sshpiperd")
var dummyauth = flag.Bool("dummyauth", false, "don't perform password check")
@ -88,13 +88,13 @@ func main() {
// Initialize contents
log.Println("Opening database...")
if err := DBInit(*dsn); err != nil {
if err := adlin.DBInit(*dsn); err != nil {
log.Fatal("Cannot open the database: ", err)
}
defer DBClose()
defer adlin.DBClose()
log.Println("Creating database...")
if err := DBCreate(); err != nil {
if err := adlin.DBCreate(); err != nil {
log.Fatal("Cannot create database: ", err)
}
@ -107,14 +107,6 @@ func main() {
Handler: StripPrefix(baseURL, Router()),
}
// Launch checker
go func() {
for {
studentsChecker()
time.Sleep(500 * time.Millisecond)
}
}()
// Serve content
go func() {
log.Fatal(srv.ListenAndServe())

View File

@ -3,21 +3,22 @@ package main
import (
"encoding/json"
"errors"
"time"
"git.nemunai.re/lectures/adlin/libadlin"
)
var PongSecret = "felixfixit"
func init() {
router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing)))
router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student Student, body []byte) (interface{}, error) {
return student.lastPongs()
router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) {
return student.LastPongs()
})))
router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly))
}
func lastPing(student Student, body []byte) (interface{}, error) {
if pongs, err := student.lastPongs(); err != nil {
func lastPing(student adlin.Student, body []byte) (interface{}, error) {
if pongs, err := student.LastPongs(); err != nil {
return nil, err
} else if len(pongs) <= 0 {
return false, nil
@ -26,33 +27,7 @@ func lastPing(student Student, body []byte) (interface{}, error) {
}
}
type Pong struct {
Date time.Time
State bool
}
func (s Student) lastPongs() (pongs []Pong, err error) {
if rows, errr := DBQuery("SELECT time, state FROM student_pong WHERE id_student = ? ORDER BY time DESC", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var p Pong
if err = rows.Scan(&p.Date, &p.State); err != nil {
return
}
pongs = append(pongs, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func stdPong(student Student, body []byte) (interface{}, error) {
func stdPong(student adlin.Student, body []byte) (interface{}, error) {
var gt givenToken
if err := json.Unmarshal(body, &gt); err != nil {
return nil, err
@ -62,5 +37,5 @@ func stdPong(student Student, body []byte) (interface{}, error) {
return nil, errors.New("This is not the expected token.")
}
return true, student.onPong(gt.Challenge == 0)
return true, student.OnPong(gt.Challenge == 0)
}

View File

@ -1,23 +1,21 @@
package main
import (
"bytes"
"crypto/hmac"
"encoding/json"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
var AuthorizedKeysLocation = "/root/.ssh/authorized_keys"
@ -26,7 +24,7 @@ var SshPiperLocation = "/var/sshpiper/"
func init() {
router.GET("/sshkeys", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
return getStudentKeys()
return adlin.GetStudentKeys()
}))
router.POST("/sshkeys", rawHandler(responseHandler(receiveKey)))
router.GET("/sshkeys/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
@ -35,153 +33,28 @@ func init() {
router.GET("/api/students/:sid/hassshkeys", apiHandler(studentHandler(hasSSHKeys)))
router.GET("/api/students/:sid/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := getStudentByLogin(ps.ByName("sid")); err != nil {
if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {
http.Error(w, "Student doesn't exist.", http.StatusNotFound)
} else {
student.dumpAuthorizedKeysFile(w)
dumpStdAuthorizedKeysFile(student, w)
}
} else if student, err := getStudent(sid); err != nil {
} else if student, err := adlin.GetStudent(sid); err != nil {
http.Error(w, "Student doesn't exist.", http.StatusNotFound)
} else {
student.dumpAuthorizedKeysFile(w)
dumpStdAuthorizedKeysFile(student, w)
}
})
}
func hasSSHKeys(student Student, body []byte) (interface{}, error) {
if keys, err := student.getKeys(); err != nil {
func hasSSHKeys(student adlin.Student, body []byte) (interface{}, error) {
if keys, err := student.GetKeys(); err != nil {
return nil, err
} else {
return len(keys) > 0, nil
}
}
type StudentKey struct {
Id int64 `json:"id"`
IdStudent int64 `json:"id_student"`
Key string `json:"key"`
Time time.Time `json:"time"`
}
func getStudentKeys() (keys []StudentKey, err error) {
if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var k StudentKey
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) getKeys() (keys []StudentKey, err error) {
if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_student = ?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var k StudentKey
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getStudentKey(id int) (k StudentKey, err error) {
err = DBQueryRow("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time)
return
}
func (s Student) NewKey(key string) (k StudentKey, err error) {
// Check key before importing it
cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
cmd.Stdin = strings.NewReader(key)
var stdoutStderr []byte
stdoutStderr, err = cmd.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = errors.New(string(stdoutStderr))
}
return
}
chunks := bytes.Fields(stdoutStderr)
keytype := string(chunks[len(chunks)-1])
minkeysize := 2048
if keytype == "(ED25519)" || keytype == "(ECDSA)" {
minkeysize = 256
}
var bits int
if bits, err = strconv.Atoi(string(chunks[0])); err != nil {
return
} else if bits < minkeysize {
err = errors.New("Keysize too small")
return
}
// Sanitize the given key
keyf := strings.Fields(key)
if len(keyf) < 2 {
err = errors.New("Unexpected key file, this should never happen")
return
}
key = keyf[0] + " " + keyf[1]
if res, err := DBExec("INSERT INTO student_keys (id_student, sshkey, time) VALUES (?, ?, ?)", s.Id, key, time.Now()); err != nil {
return StudentKey{}, err
} else if kid, err := res.LastInsertId(); err != nil {
return StudentKey{}, err
} else {
s.UnlockNewChallenge(len(challenges), "")
return StudentKey{kid, s.Id, key, time.Now()}, nil
}
}
func (k StudentKey) GetStudent() (Student, error) {
return getStudent(int(k.IdStudent))
}
func (k StudentKey) Update() (int64, error) {
if res, err := DBExec("UPDATE student_keys SET id_student = ?, sshkey = ?, time = ? WHERE id_key = ?", k.IdStudent, k.Key, k.Time, k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func (k StudentKey) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
var gt givenToken
if err := json.Unmarshal(body, &gt); err != nil {
@ -193,7 +66,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
return nil, err
}
if std, err := getStudentByLogin(gt.Login); err != nil {
if std, err := adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err
} else if len(gt.Data) < 2 {
return nil, errors.New("No key found!")
@ -207,7 +80,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
if expectedToken, err := GenerateToken(pkey, 0, data...); err != nil {
return nil, err
} else if ! hmac.Equal(expectedToken, gt.token) {
} else if !hmac.Equal(expectedToken, gt.token) {
return nil, errors.New("This is not the expected token.")
}
@ -240,7 +113,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
}
defer file.Close()
std.dumpAuthorizedKeysFile(file)
dumpStdAuthorizedKeysFile(std, file)
os.Symlink(path.Join(SshPiperLocation, "sshpiper_upstream"), path.Join(SshPiperLocation, std.Login, "sshpiper_upstream"))
os.Symlink(path.Join(SshPiperLocation, "id_rsa"), path.Join(SshPiperLocation, std.Login, "id_rsa"))
}
@ -254,7 +127,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
func dumpAuthorizedKeysFile(w io.Writer) {
seen := map[string]interface{}{}
if keys, _ := getStudentKeys(); keys != nil {
if keys, _ := adlin.GetStudentKeys(); keys != nil {
for _, k := range keys {
if _, exists := seen[k.Key]; exists {
continue
@ -268,10 +141,10 @@ func dumpAuthorizedKeysFile(w io.Writer) {
}
}
func (s Student) dumpAuthorizedKeysFile(w io.Writer) {
func dumpStdAuthorizedKeysFile(s adlin.Student, w io.Writer) {
seen := map[string]interface{}{}
if keys, _ := s.getKeys(); keys != nil {
if keys, _ := s.GetKeys(); keys != nil {
for _, k := range keys {
if _, exists := seen[k.Key]; exists {
continue

View File

@ -1,37 +1,36 @@
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
func init() {
router.GET("/api/progress", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
if stds, err := getStudents(); err != nil {
if stds, err := adlin.GetStudents(); err != nil {
return nil, err
} else {
ret := map[string]map[string]UnlockedChallenge{}
ret := map[string]map[string]adlin.UnlockedChallenge{}
for _, std := range stds {
if sts, err := std.getStates(); err == nil {
ret[std.Login] = map[string]UnlockedChallenge{}
if sts, err := std.GetStates(); err == nil {
ret[std.Login] = map[string]adlin.UnlockedChallenge{}
for _, s := range sts {
ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s
}
if pongs, err := std.lastPongs(); err == nil && len(pongs) > 0 {
ret[std.Login]["ping"] = UnlockedChallenge{
if pongs, err := std.LastPongs(); err == nil && len(pongs) > 0 {
ret[std.Login]["ping"] = adlin.UnlockedChallenge{
IdStudent: std.Id,
Time: pongs[0].Date,
Value: pongs[0].State,
Time: pongs[0].Date,
Value: pongs[0].State,
}
} else if err != nil {
log.Println(err)
@ -43,23 +42,23 @@ func init() {
}))
router.GET("/api/students/", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) {
return getStudents()
return adlin.GetStudents()
}))
router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent)))
router.GET("/api/students/:sid/", apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) {
func(std adlin.Student, _ []byte) (interface{}, error) {
return std, nil
})))
router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent))))
router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) {
func(std adlin.Student, _ []byte) (interface{}, error) {
return std.Delete()
}))))
router.GET("/api/students/:sid/progress", apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) {
ret := map[string]UnlockedChallenge{}
func(std adlin.Student, _ []byte) (interface{}, error) {
ret := map[string]adlin.UnlockedChallenge{}
if sts, err := std.getStates(); err == nil {
if sts, err := std.GetStates(); err == nil {
for _, s := range sts {
ret[fmt.Sprintf("%d", s.Challenge)] = s
}
@ -69,102 +68,12 @@ func init() {
})))
}
type Student struct {
Id int64 `json:"id"`
Login string `json:"login"`
Time *time.Time `json:"time"`
IP *string `json:"ip"`
MAC *string `json:"mac"`
}
type uploadedStudent struct {
Login string `json:"login"`
IP string `json:"ip"`
MAC string `json:"mac"`
}
func getStudents() (students []Student, err error) {
if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var s Student
if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil {
return
}
students = append(students, s)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getStudent(id int) (s Student, err error) {
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC)
return
}
func getStudentByLogin(login string) (s Student, err error) {
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC)
return
}
func studentExists(login string) bool {
var z int
err := DBQueryRow("SELECT 1 FROM students WHERE login=?", login).Scan(&z)
return err == nil && z == 1
}
func NewStudent(login string) (Student, error) {
t := time.Now()
if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil {
return Student{}, err
} else if sid, err := res.LastInsertId(); err != nil {
return Student{}, err
} else {
return Student{sid, login, &t, nil, nil}, nil
}
}
func (s Student) GetPKey() []byte {
return hmac.New(sha512.New512_224, []byte(sharedSecret)).Sum([]byte(s.Login))
}
func (s Student) Update() (int64, error) {
if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func (s Student) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func ClearStudents() (int64, error) {
if res, err := DBExec("DELETE FROM students"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
var err error
var std uploadedStudent
@ -172,22 +81,22 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
return nil, err
}
var exist Student
if exist, err = getStudentByLogin(strings.TrimSpace(std.Login)); err != nil {
if exist, err = NewStudent(strings.TrimSpace(std.Login)); err != nil {
var exist adlin.Student
if exist, err = adlin.GetStudentByLogin(strings.TrimSpace(std.Login)); err != nil {
if exist, err = adlin.NewStudent(strings.TrimSpace(std.Login)); err != nil {
return nil, err
}
}
exist.registerAccess(std.IP, std.MAC)
exist.RegisterAccess(std.IP, std.MAC)
ip := fmt.Sprintf("172.23.0.%d", exist.IPSuffix())
ip := fmt.Sprintf("172.23.0.%d", IPSuffix(exist))
exist.IP = &ip
return exist, nil
}
func updateStudent(current Student, body []byte) (interface{}, error) {
var new Student
func updateStudent(current adlin.Student, body []byte) (interface{}, error) {
var new adlin.Student
if err := json.Unmarshal(body, &new); err != nil {
return nil, err
}
@ -196,83 +105,3 @@ func updateStudent(current Student, body []byte) (interface{}, error) {
current.Time = new.Time
return current.Update()
}
type UnlockedChallenge struct {
Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"`
Value interface{} `json:"value,omitempty"`
}
func (s Student) getStates() (ucs []UnlockedChallenge, err error) {
if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u UnlockedChallenge
u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) getStatesByChallenge() (ucs []UnlockedChallenge, err error) {
if rows, errr := DBQuery("SELECT id_st, challenge, MIN(time), value FROM student_challenges WHERE id_student = ? GROUP BY challenge, id_student", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var u UnlockedChallenge
u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) {
if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil {
return UnlockedChallenge{}, err
} else if utid, err := res.LastInsertId(); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err
}
}
func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) {
if _, err := DBExec("UPDATE student_challenges SET time = ?, value = ? WHERE id_student = ? AND challenge = ?", time.Now(), value, s.Id, challenge); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err
}
}
func (s Student) registerAccess(ip, mac string) error {
if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil {
return err
} else if _, err := res.LastInsertId(); err != nil {
return err
} else {
return err
}
}

View File

@ -1,8 +1,6 @@
package main
import (
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"encoding/hex"
"encoding/json"
@ -12,10 +10,10 @@ import (
"net"
"net/http"
"os/exec"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
)
func init() {
@ -29,19 +27,19 @@ func init() {
}
})
router.GET("/api/wg/", apiAuthHandler(showWgTunnel))
router.GET("/api/wginfo", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) {
router.GET("/api/wginfo", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getTunnelInfo(student.Id), nil
}))
router.POST("/api/wg/", apiAuthHandler(genWgToken))
router.POST("/api/wg/:token", getWgTunnelInfo)
}
func showWgTunnel(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
func showWgTunnel(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
// Get tunnels assigned to the student
return student.GetTunnelTokens()
}
func genWgToken(student Student, ps httprouter.Params, body []byte) (interface{}, error) {
func genWgToken(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
// Generate a token to access related wg info
return student.NewTunnelToken()
}
@ -58,12 +56,12 @@ type TunnelInfo struct {
func getTunnelInfo(student int64) TunnelInfo {
srv_pubkey, _ := base64.StdEncoding.DecodeString("uSpqyYovvP4OG6wDxZ0Qkq45MfyK58PMUuPaLesY8FI=")
return TunnelInfo{
Status: "OK",
Status: "OK",
SrvPubKey: srv_pubkey,
SrvPort: 42912,
CltIPv6: studentIP(student),
CltRange: 80,
SrvGW6: "2a01:e0a:2b:2252::1",
SrvPort: 42912,
CltIPv6: adlin.StudentIP(student),
CltRange: 80,
SrvGW6: "2a01:e0a:2b:2252::1",
}
}
@ -92,7 +90,7 @@ func getWgTunnelInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Param
return
}
token, err := GetTunnelToken(tokendec[:n])
token, err := adlin.GetTunnelToken(tokendec[:n])
if err != nil {
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
return
@ -127,127 +125,8 @@ PersistentKeepalive = 5
`, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6)))
}
type TunnelToken struct {
token []byte
TokenText string
IdStudent int64
PubKey []byte
Time time.Time
Dump *WGDump
}
func GetTunnelToken(token []byte) (t TunnelToken, err error) {
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time)
if err == nil && t.PubKey != nil {
if wgd, errr := readWgDump(); errr == nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v
}
}
}
return
}
func tokenFromText(token string) []byte {
sha := sha512.Sum512([]byte(token))
return sha[:]
}
func (student Student) NewTunnelToken() (t TunnelToken, err error) {
tok := make([]byte, 7)
if _, err = rand.Read(tok); err != nil {
return
}
t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "#", -1), "l", "$", -1), "I", ">", -1)
t.token = tokenFromText(t.TokenText)
t.IdStudent = student.Id
_, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time) VALUES (?, ?, ?, ?)", t.token, t.TokenText, student.Id, time.Now())
return
}
func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) {
if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil {
return nil, errr
} else if wgd, errr := readWgDump(); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var t TunnelToken
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time); err != nil {
return
}
if t.PubKey != nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v
}
}
ts = append(ts, t)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) {
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time FROM student_tunnel_tokens WHERE token = ? AND id_student = ? ORDER BY time DESC", token, student.Id).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time)
if err == nil && t.PubKey != nil {
if wgd, errr := readWgDump(); errr == nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v
}
}
}
return
}
func (t *TunnelToken) Update() (int64, error) {
newtoken := tokenFromText(t.TokenText)
tm := time.Now()
if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.token); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
t.token = newtoken
t.Time = tm
return nb, err
}
}
func GetStudentsTunnels() (ts []TunnelToken, err error) {
if rows, errr := DBQuery("SELECT T.token, T.token_text, T.id_student, T.pubkey, T.time FROM student_tunnel_tokens T INNER JOIN (SELECT B.id_student, MAX(B.time) AS time FROM student_tunnel_tokens B WHERE B.pubkey IS NOT NULL GROUP BY id_student) L ON T.id_student = L.id_student AND T.time = L.time"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var t TunnelToken
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time); err != nil {
return
}
ts = append(ts, t)
}
err = rows.Err()
return
}
}
func studentIP(idstd int64) net.IP {
return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd))
}
func GenWGConfig(w io.Writer) (error) {
ts, err := GetStudentsTunnels()
func GenWGConfig(w io.Writer) error {
ts, err := adlin.GetStudentsTunnels()
if err != nil {
return err
}
@ -261,46 +140,13 @@ func GenWGConfig(w io.Writer) (error) {
#IdStudent = %d
PublicKey = %s
AllowedIPs = %s/%d
`, t.IdStudent, base64.StdEncoding.EncodeToString(t.PubKey), studentIP(t.IdStudent), 80)))
`, t.IdStudent, base64.StdEncoding.EncodeToString(t.PubKey), adlin.StudentIP(t.IdStudent), 80)))
}
return nil
}
func syncWgConf() (err error) {
_, err = exec.Command("sh", "/root/wg-sync.sh").Output()
return
}
type WGDump struct {
PubKey string
PSK string
Endpoint string
AllowedIPs string
LastHandS string
RX string
TX string
KeepAlive string
}
func readWgDump() (wgd map[string]WGDump, err error) {
out, errr := exec.Command("wg", "show", "wg-adlin", "dump").Output()
if errr != nil {
return nil, errr
}
wgd = map[string]WGDump{}
for _, line := range strings.Split(string(out), "\n") {
cols := strings.Fields(line)
if len(cols) != 8 {
continue
}
wgd[cols[0]] = WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]}
}
return
}