Compare commits

...

23 commits

Author SHA1 Message Date
caaa30c747 tuto2: fix dead URL 2021-03-08 13:44:42 +01:00
405f3d6395 checker: improve matrix check 2021-03-08 13:44:23 +01:00
78c42e312c checker: add test for Matrix 2021-03-07 17:09:11 +01:00
07e91271f4 checker: better handle DNS checks 2021-03-07 12:42:05 +01:00
456694c330 maatma: add mistake indications on home page 2021-03-07 12:41:47 +01:00
6d8f38d749 Use pointer instead of struct 2021-03-07 12:41:47 +01:00
853477e54a checker: deep redesign 2021-03-07 12:41:47 +01:00
4a27c9367e checker: default NS as const 2021-03-07 12:38:37 +01:00
059e3cabec checker: fix problematic file descriptor leak 2021-03-07 12:38:37 +01:00
2bd6b3f18b maatma: add help on pages 2021-03-07 12:38:37 +01:00
8712e33c5a maatma: publish progression on frontpage 2021-03-07 12:38:37 +01:00
f47eb68505 dashboard: extract progress and visual changes 2021-03-07 12:38:37 +01:00
764a790b11 Update tutorial2 2021-03-07 12:38:37 +01:00
5a4650f70e token-validator: update to handle custom domains 2021-03-07 12:38:37 +01:00
efab34d551 checker: don't validate http challenge if error status returns 2021-03-07 12:38:37 +01:00
9cd237daff checker: for each tun IP 2021-03-07 12:38:37 +01:00
0af7437693 libadlin: avoid # char, anchor... 2021-03-07 12:38:37 +01:00
b6eb652929 token-validator: display custom IP 2021-03-07 12:38:36 +01:00
8427a0adb8 token-validator: display user associated domain and delegation 2021-03-07 12:38:36 +01:00
d28b14fa50 token-validator: better calculate IP contained 2021-03-03 18:44:26 +01:00
6fcdc44952 Add missing deps for tuto2 2021-03-03 18:43:11 +01:00
54e1505db9 Update tuto2 for 2022 2021-03-03 18:42:41 +01:00
da1920673d token-validator: use SuffixIP, can modify it and can delete tunnels 2021-03-02 19:08:42 +01:00
37 changed files with 1280 additions and 364 deletions

View file

@ -50,6 +50,10 @@ pkg/wg-manager: pkg/wg-manager/cmd/register.go pkg/wg-manager/cmd/main.go pkg/wg
server.iso: server.yml students.csv ssl/fullchain.pem ssl/privkey.pem challenge-initrd.img pkg/arp-spoofer pkg/login-validator pkg/monit pkg/postfix pkg/tftpd pkg/unbound pkg/wg-manager challenge-kernel login-initrd.img server.iso: server.yml students.csv ssl/fullchain.pem ssl/privkey.pem challenge-initrd.img pkg/arp-spoofer pkg/login-validator pkg/monit pkg/postfix pkg/tftpd pkg/unbound pkg/wg-manager challenge-kernel login-initrd.img
linuxkit build -docker -format iso-bios $< linuxkit build -docker -format iso-bios $<
pkg/debian-tuto2: pkg/debian-tuto2/sshd_config pkg/debian-tuto2/gai.conf pkg/debian-tuto2/isolinux.cfg pkg/debian-tuto2/build.yml pkg/debian-tuto2/default.script pkg/debian-tuto2/issue pkg/debian-tuto2/Dockerfile
linuxkit pkg build -org nemunaire pkg/debian-tuto2/
touch pkg/debian-tuto2
tuto2-kernel: tuto2.yml tuto2-kernel: tuto2.yml
linuxkit build -docker $< linuxkit build -docker $<
tuto2-initrd.img: tuto2.yml tuto2-initrd.img: tuto2.yml
@ -57,7 +61,7 @@ tuto2-initrd.img: tuto2.yml
tuto2-cmdline: tuto2.yml tuto2-cmdline: tuto2.yml
linuxkit build -docker $< linuxkit build -docker $<
tuto2.iso: tuto2.yml tuto2-kernel tuto2-initrd.img tuto2-cmdline tuto2.iso: tuto2.yml pkg/debian-tuto2 tuto2-kernel tuto2-initrd.img tuto2-cmdline
linuxkit build -docker -format iso-bios $< linuxkit build -docker -format iso-bios $<
tuto2-srs.iso: tuto2.iso pkg/debian-tuto2/isolinux.cfg tuto2-srs.iso: tuto2.iso pkg/debian-tuto2/isolinux.cfg

View file

@ -1,12 +1,14 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -15,6 +17,10 @@ import (
"git.nemunai.re/lectures/adlin/libadlin" "git.nemunai.re/lectures/adlin/libadlin"
) )
const (
DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1"
)
var verbose = false var verbose = false
// ICMP // ICMP
@ -25,6 +31,7 @@ func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) {
if err != nil { if err != nil {
return return
} }
defer pinger.Stop()
pinger.Timeout = time.Second * 5 pinger.Timeout = time.Second * 5
pinger.Count = 1 pinger.Count = 1
@ -46,7 +53,7 @@ func get_GLUE(domain string) (aaaa net.IP, err error) {
m.SetEdns0(4096, true) m.SetEdns0(4096, true)
var r *dns.Msg var r *dns.Msg
r, _, err = client.Exchange(m, "[2a01:e0a:25a:9160::2]:53") r, _, err = client.Exchange(m, "[2a01:e0a:2b:2250::b]:53")
if err != nil { if err != nil {
return return
} }
@ -86,8 +93,10 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) {
err = errors.New("failed to get a valid answer") err = errors.New("failed to get a valid answer")
} }
if len(r.Answer) > 0 { for _, answer := range r.Answer {
aaaa = r.Answer[0].(*dns.AAAA).AAAA if t, ok := answer.(*dns.AAAA); ok {
aaaa = t.AAAA
}
} }
return return
@ -95,19 +104,33 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) {
// PORT 80 // PORT 80
func check_http(ip string) (err error) { func check_http(ip, dn string) (err error) {
client := &http.Client{ client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}, },
} }
req, errr := http.NewRequest("GET", fmt.Sprintf("http://[%s]/", ip), nil)
if errr != nil {
return errr
}
if dn != "" {
req.Header.Add("Host", strings.TrimSuffix(dn, "."))
}
var resp *http.Response var resp *http.Response
resp, err = client.Get(fmt.Sprintf("http://[%s]/", ip)) resp, err = client.Do(req)
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
if dn != "" && resp.StatusCode >= 400 {
return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status)
}
_, err = ioutil.ReadAll(resp.Body) _, err = ioutil.ReadAll(resp.Body)
return return
} }
@ -116,18 +139,97 @@ func check_http(ip string) (err error) {
func check_https(domain, ip string) (err error) { func check_https(domain, ip string) (err error) {
var resp *http.Response var resp *http.Response
resp, err = http.Get(fmt.Sprintf("https://%s/", domain)) resp, err = http.Get(fmt.Sprintf("https://%s/", strings.TrimSuffix(domain, ".")))
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
loc := resp.Header.Get("Location")
if loc != "" && strings.HasSuffix(dns.Fqdn(loc), domain) {
if dns.Fqdn(loc) == domain {
return fmt.Errorf("Redirection loop %s redirect to %s", domain, loc)
} else if err = check_https(dns.Fqdn(loc), ip); err != nil {
return fmt.Errorf("Error after following redirection to %s: %w", loc, err)
} else {
return
}
}
}
if resp.StatusCode >= 300 {
return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status)
}
_, err = ioutil.ReadAll(resp.Body) _, err = ioutil.ReadAll(resp.Body)
return return
} }
// MATRIX
type matrix_result struct {
WellKnownResult struct {
Result string `json:"result"`
}
DNSResult struct {
SRVError *struct {
Message string
}
}
ConnectionReports map[string]struct {
Errors []string
}
ConnectionErrors map[string]struct {
Message string
}
Version struct {
Name string `json:"name"`
Version string `json:"version"`
}
FederationOK bool
}
func check_matrix(domain string) (version string, err error) {
var resp *http.Response
resp, err = http.Get(fmt.Sprintf("https://federation-tester.adlin.nemunai.re/api/report?server_name=%s", strings.TrimSuffix(domain, ".")))
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain)
}
var federationTest matrix_result
if federationTest.FederationOK {
version = federationTest.Version.Name + " " + federationTest.Version.Version
return version, nil
} else if err = json.NewDecoder(resp.Body).Decode(&federationTest); err != nil {
log.Printf("Error in chech_matrix, when decoding json:", err.Error())
return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain)
} else if federationTest.DNSResult.SRVError != nil && federationTest.WellKnownResult.Result != "" {
return "", fmt.Errorf("%s OR %s", federationTest.DNSResult.SRVError.Message, federationTest.WellKnownResult.Result)
} else if len(federationTest.ConnectionErrors) > 0 {
var msg strings.Builder
for srv, cerr := range federationTest.ConnectionErrors {
if msg.Len() > 0 {
msg.WriteString("; ")
}
msg.WriteString(srv)
msg.WriteString(": ")
msg.WriteString(cerr.Message)
}
return "", fmt.Errorf("Connection errors: %s", msg.String())
} else {
return "", fmt.Errorf("An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s", domain)
}
}
// Main // Main
func minTunnelVersion(std adlin.Student) (int, error) { func minTunnelVersion(std *adlin.Student, suffixip int) (int, error) {
tunnels, err := std.GetTunnelTokens() tunnels, err := std.GetTunnelTokens()
if err != nil { if err != nil {
return 0, err return 0, err
@ -139,7 +241,7 @@ func minTunnelVersion(std adlin.Student) (int, error) {
continue continue
} }
if tunnel.Dump != nil && tunnel.Version < minversion { if tunnel.Dump != nil && tunnel.Version < minversion && suffixip == tunnel.SuffixIP {
minversion = tunnel.Version minversion = tunnel.Version
} }
} }
@ -153,110 +255,175 @@ func studentsChecker() {
log.Println("Unable to check students:", err) log.Println("Unable to check students:", err)
return return
} }
log.Println("Checking students...") check_matrix_for := (time.Now().Second()/30)*5 + time.Now().Minute()%5
for _, s := range students { log.Printf("Checking students... (std_matrix%%10=%d)\n", check_matrix_for)
for istd, s := range students {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
// Check ping // Check ping
std := s std := s
stdIP := adlin.StudentIP(std.Id).String() + "1" tuns, err := std.GetActivesTunnels()
go check_ping(stdIP, func(pkt *ping.Packet) { if err != nil {
tunnel_version, err := minTunnelVersion(std) continue
if verbose { }
log.Printf("%s PONG; version=%d (%v)\n", std.Login, tunnel_version, err)
}
std.OnPong(true)
if tunnel_version == 2147483647 || tunnel_version == 0 {
log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err)
return
}
dnsIP := stdIP
// Is GLUE defined?
if glueIP, err := get_GLUE(std.MyDelegatedDomain()); glueIP != nil {
dnsIP = glueIP.String()
for _, tun := range tuns {
stdIP := tun.GetStudentIP()
go check_ping(stdIP, func(pkt *ping.Packet) {
tunnel_version, err := minTunnelVersion(std, tun.SuffixIP)
if verbose { if verbose {
log.Printf("%s has defined GLUE: %s\n", std.Login, dnsIP) log.Printf("%s PONG (on %x); version=%d (%v)\n", std.Login, tun.SuffixIP, tunnel_version, err)
} }
} else if err != nil { std.OnPong(true)
log.Printf("%s and GLUE: %s\n", std.Login, err)
}
// Check DNS if tunnel_version == 2147483647 || tunnel_version == 0 {
if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil { log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err)
if verbose { return
log.Printf("%s just unlocked DNS challenge\n", std.Login)
} }
if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+2, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+2, ""); err != nil { dnsIP := stdIP
var glueErr error
// Is GLUE defined?
if glueIP, err := get_GLUE(std.MyDelegatedDomain()); glueIP != nil {
dnsIP = glueIP.String()
if verbose {
log.Printf("%s has defined GLUE: %s\n", std.Login, dnsIP)
}
} else if err != nil {
log.Printf("%s and GLUE: %s\n", std.Login, err)
glueErr = err
}
// Check DNS
if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil {
if addr == nil {
dnsAt := " at " + dnsIP
if glueErr != nil {
dnsAt = " + there is a problem with the GLUE record: " + glueErr.Error()
}
if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, fmt.Errorf("%s: empty response from the server%s", std.MyDelegatedDomain(), dnsAt)); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
} else {
if verbose {
log.Printf("%s just unlocked DNS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+3, addr.String()); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
// Check HTTP with DNS
if glueErr != nil {
std.RegisterChallengeError(100*(tunnel_version-1)+4, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err))
} else if err := check_http(addr.String(), std.MyDelegatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked HTTP challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+4, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+4, err)
if verbose {
log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
}
}
// Check HTTPs with DNS
if glueErr != nil {
std.RegisterChallengeError(100*(tunnel_version-1)+5, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err))
} else if err := check_https(std.MyDelegatedDomain(), addr.String()); err == nil {
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+5, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+5, err)
if verbose {
log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
}
}
// Check Matrix (only if GLUE Ok and)
if glueErr == nil && istd%10 == check_matrix_for {
if v, err := check_matrix(std.MyDelegatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked Matrix challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+6, v); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+6, err)
if verbose {
log.Printf("%s and Matrix: %s\n", std.Login, err)
}
}
}
}
} else {
if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, err); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
if verbose {
log.Printf("%s and DNS: %s\n", std.Login, err)
}
}
// Check HTTP without DNS
if err := check_http(stdIP, ""); err == nil {
if verbose {
log.Printf("%s just unlocked HTTP IP (without DNS) challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+0, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
} }
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+0, err)
if verbose {
log.Printf("%s and HTTP IP (without DNS): %s\n", std.Login, err)
}
} }
// Check HTTP with DNS // Check DNS for association
if addr == nil { if addr, err := check_dns(std.MyAssociatedDomain(), DEFAULT_RESOLVER); err == nil {
log.Printf("%s and HTTP (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String()) // Check HTTP on delegated domain
} else if err := check_http(addr.String()); err == nil { if err := check_http(addr.String(), std.MyAssociatedDomain()); err == nil {
if verbose { if verbose {
log.Printf("%s just unlocked HTTP challenge\n", std.Login) log.Printf("%s just unlocked HTTP (without DNS) challenge\n", std.Login)
} }
if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil { if _, err := std.UnlockChallenge(100*(tunnel_version-1)+1, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
} }
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+1, err)
if verbose {
log.Printf("%s and HTTP (without DNS): %s\n", std.Login, err)
}
}
// Check HTTPs without DNS
if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil {
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+2, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(100*(tunnel_version-1)+2, err)
if verbose {
log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err)
}
} }
} else if verbose {
log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
} }
// Check HTTPs with DNS return
if addr == nil { })
log.Printf("%s and HTTPS (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String()) }
} else if err := check_https(std.MyDelegatedDomain(), addr.String()); err == nil {
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else if verbose {
log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
}
} else {
// Check HTTP without DNS
if err := check_http(stdIP); err == nil {
if verbose {
log.Printf("%s just unlocked HTTP challenge\n", std.Login)
}
if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else if verbose {
log.Printf("%s and HTTP (without DNS): %s\n", std.Login, err)
}
// Check HTTPs without DNS
if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil {
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else if verbose {
log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err)
}
}
return
})
} }
} }

View file

@ -56,7 +56,9 @@ func DBCreate() (err error) {
CREATE TABLE IF NOT EXISTS students( CREATE TABLE IF NOT EXISTS students(
id_student INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_student INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
login VARCHAR(255) NOT NULL UNIQUE, login VARCHAR(255) NOT NULL UNIQUE,
time TIMESTAMP NOT NULL time TIMESTAMP NOT NULL,
associatedDomain VARCHAR(255) UNIQUE,
delegatedDomain VARCHAR(255) UNIQUE
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
`); err != nil { `); err != nil {
return return
@ -93,6 +95,19 @@ CREATE TABLE IF NOT EXISTS student_challenges(
CONSTRAINT token_found UNIQUE (id_student,challenge), CONSTRAINT token_found UNIQUE (id_student,challenge),
FOREIGN KEY(id_student) REFERENCES students(id_student) FOREIGN KEY(id_student) REFERENCES students(id_student)
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
`); err != nil {
return err
}
if _, err := db.Exec(`
CREATE TABLE IF NOT EXISTS student_challenge_errors(
id_st INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
id_student INTEGER NOT NULL,
challenge INTEGER NOT NULL,
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
error VARCHAR(255) NOT NULL,
CONSTRAINT token_found UNIQUE (id_student,challenge),
FOREIGN KEY(id_student) REFERENCES students(id_student)
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
`); err != nil { `); err != nil {
return err return err
} }

View file

@ -6,22 +6,39 @@ import (
) )
const ( const (
AssociatedDomainSuffix = "adlin2021.p0m.fr." AssociatedDomainSuffix = "adlin2022.p0m.fr."
DelegatedDomainSuffix = "srs.p0m.fr." DelegatedDomainSuffix = "srs.p0m.fr."
) )
func (student Student) MyDelegatedDomain() string { func (student *Student) MyDelegatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix) if student.DelegatedDomain != nil {
return *student.DelegatedDomain
} else {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix)
}
} }
func (student Student) MyAssociatedDomain() string { func (student *Student) DefaultAssociatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix) return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix)
} }
func (student Student) GetAssociatedDomains() (ds []string) { func (student *Student) MyAssociatedDomain() string {
if student.AssociatedDomain != nil {
return *student.AssociatedDomain
} else {
return student.DefaultAssociatedDomain()
}
}
func (student *Student) GetAssociatedDomains() (ds []string) {
defdn := student.DefaultAssociatedDomain()
ds = append(ds, defdn)
studentDomain := student.MyAssociatedDomain() studentDomain := student.MyAssociatedDomain()
ds = append(ds, studentDomain) if defdn != studentDomain {
ds = append(ds, studentDomain)
}
return return
} }

View file

@ -9,14 +9,14 @@ type Pong struct {
State bool State bool
} }
func (s Student) LastPongs() (pongs []Pong, err error) { 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 { if rows, errr := DBQuery("SELECT time, state FROM student_pong WHERE id_student = ? ORDER BY time DESC", s.Id); errr != nil {
return nil, errr return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var p Pong p := &Pong{}
if err = rows.Scan(&p.Date, &p.State); err != nil { if err = rows.Scan(&p.Date, &p.State); err != nil {
return return
} }

View file

@ -11,40 +11,41 @@ type Session struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
} }
func GetSession(id []byte) (s Session, err error) { func GetSession(id []byte) (s *Session, err error) {
s = new(Session)
err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time) err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time)
return return
} }
func NewSession() (Session, error) { func NewSession() (*Session, error) {
session_id := make([]byte, 255) session_id := make([]byte, 255)
if _, err := rand.Read(session_id); err != nil { if _, err := rand.Read(session_id); err != nil {
return Session{}, err return nil, err
} else if _, err := DBExec("INSERT INTO student_sessions (id_session, time) VALUES (?, ?)", session_id, time.Now()); err != nil { } else if _, err := DBExec("INSERT INTO student_sessions (id_session, time) VALUES (?, ?)", session_id, time.Now()); err != nil {
return Session{}, err return nil, err
} else { } else {
return Session{session_id, nil, time.Now()}, nil return &Session{session_id, nil, time.Now()}, nil
} }
} }
func (student Student) NewSession() (Session, error) { func (student *Student) NewSession() (*Session, error) {
session_id := make([]byte, 255) session_id := make([]byte, 255)
if _, err := rand.Read(session_id); err != nil { if _, err := rand.Read(session_id); err != nil {
return Session{}, err return nil, err
} else if _, err := DBExec("INSERT INTO student_sessions (id_session, id_student, time) VALUES (?, ?, ?)", session_id, student.Id, time.Now()); err != nil { } else if _, err := DBExec("INSERT INTO student_sessions (id_session, id_student, time) VALUES (?, ?, ?)", session_id, student.Id, time.Now()); err != nil {
return Session{}, err return nil, err
} else { } else {
return Session{session_id, &student.Id, time.Now()}, nil return &Session{session_id, &student.Id, time.Now()}, nil
} }
} }
func (s Session) SetStudent(student Student) (Session, error) { func (s *Session) SetStudent(student *Student) (*Session, error) {
s.IdStudent = &student.Id s.IdStudent = &student.Id
_, err := s.Update() _, err := s.Update()
return s, err return s, err
} }
func (s Session) Update() (int64, error) { func (s *Session) Update() (int64, error) {
if res, err := DBExec("UPDATE student_sessions SET id_student = ?, time = ? WHERE id_session = ?", s.IdStudent, s.Time, s.Id); err != nil { if res, err := DBExec("UPDATE student_sessions SET id_student = ?, time = ? WHERE id_session = ?", s.IdStudent, s.Time, s.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
@ -54,7 +55,7 @@ func (s Session) Update() (int64, error) {
} }
} }
func (s Session) Delete() (int64, error) { func (s *Session) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM student_sessions WHERE id_session = ?", s.Id); err != nil { if res, err := DBExec("DELETE FROM student_sessions WHERE id_session = ?", s.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {

View file

@ -16,14 +16,14 @@ type StudentKey struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
} }
func GetStudentKeys() (keys []StudentKey, err error) { func GetStudentKeys() (keys []*StudentKey, err error) {
if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil {
return nil, errr return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var k StudentKey k := &StudentKey{}
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return return
} }
@ -37,14 +37,14 @@ func GetStudentKeys() (keys []StudentKey, err error) {
} }
} }
func (s Student) GetKeys() (keys []StudentKey, err error) { 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 { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_student = ?", s.Id); errr != nil {
return nil, errr return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var k StudentKey k := &StudentKey{}
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return return
} }
@ -58,12 +58,13 @@ func (s Student) GetKeys() (keys []StudentKey, err error) {
} }
} }
func getStudentKey(id int) (k StudentKey, err error) { func getStudentKey(id int) (k *StudentKey, err error) {
k = new(StudentKey)
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) 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 return
} }
func (s Student) NewKey(key string) (k StudentKey, err error) { func (s *Student) NewKey(key string) (k *StudentKey, err error) {
// Check key before importing it // Check key before importing it
cmd := exec.Command("ssh-keygen", "-l", "-f", "-") cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
cmd.Stdin = strings.NewReader(key) cmd.Stdin = strings.NewReader(key)
@ -101,20 +102,20 @@ func (s Student) NewKey(key string) (k StudentKey, err error) {
key = keyf[0] + " " + keyf[1] 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 { if res, err := DBExec("INSERT INTO student_keys (id_student, sshkey, time) VALUES (?, ?, ?)", s.Id, key, time.Now()); err != nil {
return StudentKey{}, err return nil, err
} else if kid, err := res.LastInsertId(); err != nil { } else if kid, err := res.LastInsertId(); err != nil {
return StudentKey{}, err return nil, err
} else { } else {
s.UnlockNewChallenge(11, "") s.UnlockNewChallenge(11, "")
return StudentKey{kid, s.Id, key, time.Now()}, nil return &StudentKey{kid, s.Id, key, time.Now()}, nil
} }
} }
func (k StudentKey) GetStudent() (Student, error) { func (k *StudentKey) GetStudent() (*Student, error) {
return GetStudent(int(k.IdStudent)) return GetStudent(int(k.IdStudent))
} }
func (k StudentKey) Update() (int64, error) { 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 { 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 return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
@ -124,7 +125,7 @@ func (k StudentKey) Update() (int64, error) {
} }
} }
func (k StudentKey) Delete() (int64, error) { func (k *StudentKey) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil { if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {

View file

@ -3,26 +3,29 @@ package adlin
import ( import (
"crypto/hmac" "crypto/hmac"
"crypto/sha512" "crypto/sha512"
"fmt"
"time" "time"
) )
type Student struct { type Student struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Login string `json:"login"` Login string `json:"login"`
Time *time.Time `json:"time"` Time *time.Time `json:"time"`
IP *string `json:"ip"` IP *string `json:"ip"`
MAC *string `json:"mac"` MAC *string `json:"mac"`
AssociatedDomain *string `json:"associated_domain,omitempty"`
DelegatedDomain *string `json:"delegated_domain,omitempty"`
} }
func GetStudents() (students []Student, err error) { 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 { if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain 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 return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var s Student s := &Student{}
if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil { if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain); err != nil {
return return
} }
students = append(students, s) students = append(students, s)
@ -35,13 +38,15 @@ func GetStudents() (students []Student, err error) {
} }
} }
func GetStudent(id int) (s Student, err error) { 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) s = new(Student)
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain 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, &s.AssociatedDomain, &s.DelegatedDomain)
return return
} }
func GetStudentByLogin(login string) (s Student, err error) { 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) s = new(Student)
err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain 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, &s.AssociatedDomain, &s.DelegatedDomain)
return return
} }
@ -51,23 +56,23 @@ func StudentExists(login string) bool {
return err == nil && z == 1 return err == nil && z == 1
} }
func NewStudent(login string) (Student, error) { func NewStudent(login string) (*Student, error) {
t := time.Now() t := time.Now()
if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil { if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil {
return Student{}, err return nil, err
} else if sid, err := res.LastInsertId(); err != nil { } else if sid, err := res.LastInsertId(); err != nil {
return Student{}, err return nil, err
} else { } else {
return Student{sid, login, &t, nil, nil}, nil return &Student{sid, login, &t, nil, nil, nil, nil}, nil
} }
} }
func (s Student) GetPKey() []byte { func (s *Student) GetPKey() []byte {
return hmac.New(sha512.New512_224, []byte(SharedSecret)).Sum([]byte(s.Login)) return hmac.New(sha512.New512_224, []byte(SharedSecret)).Sum([]byte(s.Login))
} }
func (s Student) Update() (int64, error) { 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 { if res, err := DBExec("UPDATE students SET login = ?, time = ?, associatedDomain = ?, delegatedDomain = ? WHERE id_student = ?", s.Login, s.Time, s.AssociatedDomain, s.DelegatedDomain, s.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
return 0, err return 0, err
@ -76,7 +81,7 @@ func (s Student) Update() (int64, error) {
} }
} }
func (s Student) Delete() (int64, error) { func (s *Student) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil { if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
@ -100,18 +105,20 @@ type UnlockedChallenge struct {
Id int64 `json:"id,omitempty"` Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"` IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"` Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"` Time *time.Time `json:"time,omitempty"`
Value interface{} `json:"value,omitempty"` Value interface{} `json:"value,omitempty"`
LastCheck *time.Time `json:"last_check,omitempty"`
Error string `json:"error,omitempty"`
} }
func (s Student) GetStates() (ucs []UnlockedChallenge, err error) { 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 { if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil {
return nil, errr return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var u UnlockedChallenge u := &UnlockedChallenge{}
u.IdStudent = s.Id u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil { if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil {
return return
@ -126,14 +133,36 @@ func (s Student) GetStates() (ucs []UnlockedChallenge, err error) {
} }
} }
func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) { func (s *Student) GetChallengeErrors() (ucs []*ErroredChallenge, err error) {
if rows, errr := DBQuery("SELECT id_st, challenge, time, error FROM student_challenge_errors WHERE id_student = ?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
u := &ErroredChallenge{}
u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Error); 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 { 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 return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var u UnlockedChallenge u := &UnlockedChallenge{}
u.IdStudent = s.Id u.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil { if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil {
return return
@ -148,25 +177,57 @@ func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) {
} }
} }
func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) { func (s *Student) UnlockChallenge(challenge int, value string) (uc *UnlockedChallenge, err error) {
if uc, err = s.UnlockNewChallenge(challenge, value); err != nil {
if uc, err = s.UpdateUnlockedChallenge(challenge, value); err != nil {
return
}
}
s.RegisterChallengeError(challenge, fmt.Errorf("OK"))
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 { 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 return nil, err
} else if utid, err := res.LastInsertId(); err != nil { } else if utid, err := res.LastInsertId(); err != nil {
return UnlockedChallenge{}, err return nil, err
} else { } else {
return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err now := time.Now()
return &UnlockedChallenge{utid, s.Id, challenge, &now, value, nil, ""}, err
} }
} }
func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) { 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 { 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 return nil, err
} else { } else {
return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err now := time.Now()
return &UnlockedChallenge{0, s.Id, challenge, &now, value, nil, ""}, err
} }
} }
func (s Student) RegisterAccess(ip, mac string) error { type ErroredChallenge struct {
Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"`
Error string `json:"error,omitempty"`
}
func (s *Student) RegisterChallengeError(challenge int, err error) error {
if _, errr := DBExec("INSERT INTO student_challenge_errors (id_student, challenge, time, error) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), err.Error()); errr == nil {
return nil
} else if _, errr := DBExec("UPDATE student_challenge_errors SET time = ?, error = ? WHERE id_student = ? AND challenge = ?", time.Now(), err.Error(), s.Id, challenge); errr != nil {
return errr
} else {
return nil
}
}
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 { if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil {
return err return err
} else if _, err := res.LastInsertId(); err != nil { } else if _, err := res.LastInsertId(); err != nil {

View file

@ -12,10 +12,19 @@ import (
"time" "time"
) )
const StdNetmask = 80
func StudentIP(idstd int64) net.IP { func StudentIP(idstd int64) net.IP {
return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd)) return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd))
} }
func StudentNet(idstd int64) *net.IPNet {
return &net.IPNet{
IP: StudentIP(idstd),
Mask: net.CIDRMask(StdNetmask, 128),
}
}
type WGDump struct { type WGDump struct {
PubKey string PubKey string
PSK string PSK string
@ -28,32 +37,32 @@ type WGDump struct {
} }
var ( var (
wgDumpCache_data map[string]WGDump = nil wgDumpCache_data map[string]*WGDump = nil
wgDumpCache_time time.Time wgDumpCache_time time.Time
wgDumpCache_mutex sync.RWMutex wgDumpCache_mutex sync.RWMutex
) )
func _readWgDump() (wgd map[string]WGDump, err error) { func _readWgDump() (wgd map[string]*WGDump, err error) {
out, errr := exec.Command("wg", "show", "wg-adlin", "dump").Output() out, errr := exec.Command("wg", "show", "wg-adlin", "dump").Output()
if errr != nil { if errr != nil {
return nil, errr return nil, errr
} }
wgd = map[string]WGDump{} wgd = map[string]*WGDump{}
for _, line := range strings.Split(string(out), "\n") { for _, line := range strings.Split(string(out), "\n") {
cols := strings.Fields(line) cols := strings.Fields(line)
if len(cols) != 8 { if len(cols) != 8 {
continue continue
} }
wgd[cols[0]] = WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]} wgd[cols[0]] = &WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]}
} }
return return
} }
func readWgDump() (wgd map[string]WGDump, err error) { func readWgDump() (wgd map[string]*WGDump, err error) {
wgDumpCache_mutex.RLock() wgDumpCache_mutex.RLock()
defer wgDumpCache_mutex.RUnlock() defer wgDumpCache_mutex.RUnlock()
@ -94,38 +103,48 @@ type TunnelToken struct {
Dump *WGDump Dump *WGDump
} }
func tokenFromText(token string) []byte { func (tt *TunnelToken) GetStudentIP() string {
if tt.SuffixIP == 0 {
return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), 1)
} else {
return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), tt.SuffixIP)
}
}
func TokenFromText(token string) []byte {
sha := sha512.Sum512([]byte(token)) sha := sha512.Sum512([]byte(token))
return sha[:] return sha[:]
} }
func GetTunnelToken(token []byte) (t TunnelToken, err error) { func GetTunnelToken(token []byte) (t *TunnelToken, err error) {
t = new(TunnelToken)
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version)
if err == nil && t.PubKey != nil { if err == nil && t.PubKey != nil {
if wgd, errr := readWgDump(); errr == nil { if wgd, errr := readWgDump(); errr == nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v t.Dump = v
} }
} }
} }
return return
} }
func (student Student) NewTunnelToken(suffixip int) (t TunnelToken, err error) { func (student *Student) NewTunnelToken(suffixip int) (t *TunnelToken, err error) {
tok := make([]byte, 7) tok := make([]byte, 7)
if _, err = rand.Read(tok); err != nil { if _, err = rand.Read(tok); err != nil {
return 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 = new(TunnelToken)
t.token = tokenFromText(t.TokenText) 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 t.IdStudent = student.Id
_, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip) _, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip)
return return
} }
func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { func (student *Student) GetTunnelTokens() (ts []*TunnelToken, err error) {
if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil {
return nil, errr return nil, errr
} else if wgd, errr := readWgDump(); errr != nil { } else if wgd, errr := readWgDump(); errr != nil {
@ -134,13 +153,13 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var t TunnelToken t := &TunnelToken{}
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil {
return return
} }
if t.PubKey != nil { if t.PubKey != nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v t.Dump = v
} }
} }
ts = append(ts, t) ts = append(ts, t)
@ -153,7 +172,7 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) {
} }
} }
func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { func (student *Student) GetActivesTunnels() (ts []*TunnelToken, err error) {
if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil {
return nil, errr return nil, errr
} else if wgd, errr := readWgDump(); errr != nil { } else if wgd, errr := readWgDump(); errr != nil {
@ -162,13 +181,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var t TunnelToken t := &TunnelToken{}
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil {
return return
} }
if t.PubKey != nil { if t.PubKey != nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v t.Dump = v
ts = append(ts, t) ts = append(ts, t)
} }
} }
@ -181,12 +200,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) {
} }
} }
func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) { func (student *Student) GetTunnelToken(token []byte) (t *TunnelToken, err error) {
t = new(TunnelToken)
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version 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, &t.SuffixIP, &t.Version) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version 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, &t.SuffixIP, &t.Version)
if err == nil && t.PubKey != nil { if err == nil && t.PubKey != nil {
if wgd, errr := readWgDump(); errr == nil { if wgd, errr := readWgDump(); errr == nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v t.Dump = v
} }
} }
} }
@ -194,7 +214,7 @@ func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) {
} }
func (t *TunnelToken) Update() (int64, error) { func (t *TunnelToken) Update() (int64, error) {
newtoken := tokenFromText(t.TokenText) newtoken := TokenFromText(t.TokenText)
tm := time.Now() tm := time.Now()
if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ?, suffixip = ?, version = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.SuffixIP, t.Version, t.token); err != nil { if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ?, suffixip = ?, version = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.SuffixIP, t.Version, t.token); err != nil {
@ -208,14 +228,24 @@ func (t *TunnelToken) Update() (int64, error) {
} }
} }
func GetStudentsTunnels() (ts []TunnelToken, err error) { func (t *TunnelToken) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM student_tunnel_tokens WHERE token = ? AND id_student = ?", t.token, t.IdStudent); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
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, T.suffixip, T.version 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 { if rows, errr := DBQuery("SELECT T.token, T.token_text, T.id_student, T.pubkey, T.time, T.suffixip, T.version 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 return nil, errr
} else { } else {
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var t TunnelToken t := &TunnelToken{}
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil {
return return
} }

View file

@ -27,7 +27,7 @@ func init() {
router.POST("/api/auth/logout", apiRawHandler(logout)) router.POST("/api/auth/logout", apiRawHandler(logout))
} }
func validateAuthToken(s adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) { func validateAuthToken(s *adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) {
return s, nil return s, nil
} }
@ -50,7 +50,7 @@ type loginForm struct {
} }
func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) { func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) {
var std adlin.Student var std *adlin.Student
if !adlin.StudentExists(username) { if !adlin.StudentExists(username) {
if std, err = adlin.NewStudent(username); err != nil { if std, err = adlin.NewStudent(username); err != nil {
return err return err
@ -60,9 +60,7 @@ func completeAuth(w http.ResponseWriter, username string, session *adlin.Session
} }
if session == nil { if session == nil {
var s adlin.Session session, err = std.NewSession()
s, err = std.NewSession()
session = &s
} else { } else {
_, err = session.SetStudent(std) _, err = session.SetStudent(std)
} }

View file

@ -111,7 +111,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par
return return
} }
if err := completeAuth(w, claims.Username, &session); err != nil { if err := completeAuth(w, claims.Username, session); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }

View file

@ -338,7 +338,7 @@ func receiveChallenge(r *http.Request, ps httprouter.Params, body []byte) (inter
return nil, errors.New("This is not the expected token.") return nil, errors.New("This is not the expected token.")
} }
var std adlin.Student var std *adlin.Student
if stdid, err := strconv.Atoi(gt.Login); err == nil { if stdid, err := strconv.Atoi(gt.Login); err == nil {
if std, err = adlin.GetStudent(stdid); err != nil { if std, err = adlin.GetStudent(stdid); err != nil {
@ -388,7 +388,7 @@ func receiveToken(r *http.Request, body []byte, chid int) (interface{}, error) {
if std, err := adlin.GetStudentByLogin(gt.Login); err != nil { if std, err := adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err return nil, err
} else { } 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) log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err)
return nil, err return nil, err
} }

View file

@ -19,7 +19,7 @@ type checkGLUE struct {
} }
func init() { func init() {
router.POST("/api/check/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { router.POST("/api/check/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
var uc checkGLUE var uc checkGLUE
if err := json.Unmarshal(body, &uc); err != nil { if err := json.Unmarshal(body, &uc); err != nil {
return nil, err return nil, err
@ -27,7 +27,7 @@ func init() {
return true, check_GLUE_respond(student, uc.Domain, uc.IP) return true, check_GLUE_respond(student, uc.Domain, uc.IP)
})) }))
} }
func check_GLUE_respond(student adlin.Student, domain string, ip string) (err error) { func check_GLUE_respond(student *adlin.Student, domain string, ip string) (err error) {
if !strings.HasPrefix(ip, adlin.StudentIP(student.Id).String()) { if !strings.HasPrefix(ip, adlin.StudentIP(student.Id).String()) {
return fmt.Errorf("%q is not your IP range") return fmt.Errorf("%q is not your IP range")
} }

View file

@ -22,103 +22,151 @@ var (
) )
func init() { func init() {
router.GET("/api/adomains/", apiAuthHandler(func(student adlin.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 return student.GetAssociatedDomains(), nil
})) }))
router.POST("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { router.POST("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
ue := &struct { ue := &struct {
Domain string `json:"domain"` Domain string `json:"domain"`
A string `json:"a"` A string `json:"a"`
AAAA string `json:"aaaa"` AAAA string `json:"aaaa"`
CNAME string `json:"cname,omitempty"`
}{} }{}
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
var aaaa net.IP if ue.Domain != "" && ue.A == "" && ue.AAAA == "" && ue.CNAME == "" {
if ue != nil && len(ue.AAAA) > 0 { student.AssociatedDomain = nil
aaaa = net.ParseIP(ue.AAAA)
}
return true, AddAssociatedDomains(student, aaaa) if _, err := student.Update(); err != nil {
return nil, err
}
return true, nil
} else if ue.CNAME != "" {
cname := dns.Fqdn(ue.CNAME)
student.AssociatedDomain = &cname
if _, err := student.Update(); err != nil {
return nil, err
}
return true, nil
} else {
var aaaa net.IP
if ue != nil && len(ue.AAAA) > 0 {
aaaa = net.ParseIP(ue.AAAA)
}
return true, AddAssociatedDomains(student, aaaa)
}
})) }))
router.GET("/api/adomains/:dn", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { router.GET("/api/adomains/:dn", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return GetAssociatedDomain(student, ps.ByName("dn")) return GetAssociatedDomain(student, ps.ByName("dn"))
})) }))
router.GET("/api/ddomains/", apiAuthHandler(func(student adlin.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 return []string{student.MyDelegatedDomain()}, nil
})) }))
router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { router.POST("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
ue := &struct {
NS string `json:"ns"`
}{}
if err := json.Unmarshal(body, &ue); err != nil {
return nil, err
}
if ue.NS == "" {
student.DelegatedDomain = nil
if _, err := student.Update(); err != nil {
return nil, err
}
return true, nil
} else {
ns := dns.Fqdn(ue.NS)
student.DelegatedDomain = &ns
if _, err := student.Update(); err != nil {
return nil, err
}
return true, nil
}
}))
router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getRRDelegatedDomain(student, ps.ByName("dn"), "") return getRRDelegatedDomain(student, ps.ByName("dn"), "")
})) }))
router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { 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") return getRRDelegatedDomain(student, ps.ByName("dn"), "NS")
})) }))
router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, AddNSDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, UpdateNSDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, DeleteRRDelegatedDomain(student, 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 adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { 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") return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA")
})) }))
router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, AddGLUEDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, UpdateGLUEDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, UpdateGLUEDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, DeleteRRDelegatedDomain(student, 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 adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { 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") return getRRDelegatedDomain(student, ps.ByName("dn"), "DS")
})) }))
router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, AddDSDelegatedDomain(student, 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 adlin.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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
@ -175,7 +223,7 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) {
return return
} }
func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err error) { func GetAssociatedDomain(student *adlin.Student, dn string) (rrs []Entry, err error) {
domains := student.GetAssociatedDomains() domains := student.GetAssociatedDomains()
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -201,7 +249,7 @@ func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err err
return return
} }
func delAssociatedDomains(student adlin.Student, dn string) (err error) { func delAssociatedDomains(student *adlin.Student, dn string) (err error) {
var adomains []Entry var adomains []Entry
adomains, err = GetAssociatedDomain(student, dn) adomains, err = GetAssociatedDomain(student, dn)
if err != nil { if err != nil {
@ -238,15 +286,15 @@ func delAssociatedDomains(student adlin.Student, dn string) (err error) {
return return
} }
func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { func AddAssociatedDomains(student *adlin.Student, aaaa net.IP) (err error) {
err = delAssociatedDomains(student, student.MyAssociatedDomain()) err = delAssociatedDomains(student, student.DefaultAssociatedDomain())
if err != nil { if err != nil {
return return
} }
if aaaa == nil { if aaaa == nil {
aaaa = net.ParseIP(adlin.StudentIP(student.Id).String() + "1") aaaa = net.ParseIP(adlin.StudentIP(student.Id).String() + "1")
} else if !strings.HasPrefix(aaaa.String(), adlin.StudentIP(student.Id).String()) { } else if !adlin.StudentNet(student.Id).Contains(aaaa) {
return errors.New("The associated IP has to be in your IP range.") return errors.New("The associated IP has to be in your IP range.")
} }
@ -257,12 +305,12 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) {
m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrA := new(dns.A) 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.DefaultAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}
rrA.A = net.IPv4(82, 64, 31, 248) rrA.A = net.IPv4(82, 64, 31, 248)
m2.Insert([]dns.RR{rrA}) m2.Insert([]dns.RR{rrA})
rrAAAA := new(dns.AAAA) rrAAAA := new(dns.AAAA)
rrAAAA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} rrAAAA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}
rrAAAA.AAAA = aaaa rrAAAA.AAAA = aaaa
m2.Insert([]dns.RR{rrAAAA}) m2.Insert([]dns.RR{rrAAAA})
@ -274,7 +322,7 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) {
return return
} }
func getRRDelegatedDomain(student adlin.Student, 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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -300,7 +348,7 @@ func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []En
return return
} }
func AddNSDelegatedDomain(student adlin.Student, 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()} { for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg) m1 := new(dns.Msg)
m1.Id = dns.Id() m1.Id = dns.Id()
@ -323,7 +371,7 @@ func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns strin
return return
} }
func UpdateNSDelegatedDomain(student adlin.Student, 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()} { for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg) m1 := new(dns.Msg)
m1.Id = dns.Id() m1.Id = dns.Id()
@ -351,7 +399,7 @@ func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns
return return
} }
func AddGLUEDelegatedDomain(student adlin.Student, 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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -387,7 +435,7 @@ func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa s
return return
} }
func UpdateGLUEDelegatedDomain(student adlin.Student, 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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -429,7 +477,7 @@ func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, old
return return
} }
func AddDSDelegatedDomain(student adlin.Student, 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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -479,7 +527,7 @@ func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata st
return return
} }
func DeleteRRDelegatedDomain(student adlin.Student, 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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {

View file

@ -65,7 +65,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return return
} else { } else {
student = &std student = std
} }
} }
@ -158,7 +158,7 @@ func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request
return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...) return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...)
} }
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) { 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) { return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
if cookie, err := r.Cookie("auth"); err != nil { if cookie, err := r.Cookie("auth"); err != nil {
return nil, errors.New("Authorization required") return nil, errors.New("Authorization required")
@ -176,7 +176,7 @@ func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{
}), access...) }), access...)
} }
func studentHandler(f func(adlin.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) { return func(ps httprouter.Params, body []byte) (interface{}, error) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {

View file

@ -24,7 +24,7 @@
height: calc(100vh / 5 - 0.3vh); height: calc(100vh / 5 - 0.3vh);
margin-left: 0.2%; margin-left: 0.2%;
margin-bottom: 0.2vh; margin-bottom: 0.2vh;
width: calc(100vw / 13 - 0.22vw); width: calc(100vw / 13 - 0.26vw);
} }
.student-title { .student-title {
width: calc(2 * (100vw / 13 - 0.22vw) + 0.2vw); width: calc(2 * (100vw / 13 - 0.22vw) + 0.2vw);
@ -47,7 +47,7 @@
.login { .login {
max-width: 122px; max-width: 122px;
} }
.badge { .badge.badge-sm {
font-size: 59%; font-size: 59%;
padding: .15em .35em; padding: .15em .35em;
} }
@ -72,12 +72,12 @@
<ul ng-if="ips" style="padding-left:0"> <ul ng-if="ips" style="padding-left:0">
<li><strong>Domaine&nbsp;:</strong> <a href="http://{{ips.adn}}/">{{ips.adn}}</a></li> <li><strong>Domaine&nbsp;:</strong> <a href="http://{{ips.adn}}/">{{ips.adn}}</a></li>
<li><strong>Délégation&nbsp;:</strong> <a href="http://{{ips.ddn}}/">{{ips.ddn}}</a></li> <li><strong>Délégation&nbsp;:</strong> <a href="http://{{ips.ddn}}/">{{ips.ddn}}</a></li>
<li><strong>IPv6&nbsp;:</strong> {{ips.wg}}1/96</li> <li><strong>IPv6&nbsp;:</strong> {{ips.wg}}/80</li>
</ul> </ul>
<div ng-repeat="(tutoid,tuto) in tuto_progress"> <div ng-repeat="(tutoid,tuto) in tuto_progress">
<hr> <hr>
<h6>TP {{tutoid+1}}</h6> <h6>TP {{tutoid+1}}</h6>
<span class="badge mr-1" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch]}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span> <span class="badge mr-1" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch] || !mychallenges[ch].recent}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
</div> </div>
</p> </p>
</div> </div>
@ -108,7 +108,7 @@
</div> </div>
</div> </div>
<div class="card student d-flex flex-column justify-content-between" ng-repeat="(login, mychallenges) in students" style="background-image: url('https://photos.cri.epita.fr/square/{{ mychallenges.img | lowercase }}')"> <div class="card student d-flex flex-column justify-content-between" ng-repeat="(login, mychallenges) in students" ng-if="login != 'nemunaire'" style="background-image: url('https://photos.cri.epita.fr/square/{{ mychallenges.img | lowercase }}')">
<h5 class="login text-truncate" title="{{ login }}"> <h5 class="login text-truncate" title="{{ login }}">
<span class="badge" ng-class="{'badge-success': mychallenges['ping'] && mychallenges['ping'].recent < 120, 'badge-info': mychallenges['ping'] && mychallenges['ping'].recent >= 120 && mychallenges['ping'].recent < 300, 'badge-warning': mychallenges['ping'] && mychallenges['ping'].recent >= 300 && mychallenges['ping'].recent < 900, 'badge-danger': mychallenges['ping'] && mychallenges['ping'].recent >= 900, 'badge-dark': !mychallenges['ping']}" title="{{ mychallenges['ping'].time }}"> <span class="badge" ng-class="{'badge-success': mychallenges['ping'] && mychallenges['ping'].recent < 120, 'badge-info': mychallenges['ping'] && mychallenges['ping'].recent >= 120 && mychallenges['ping'].recent < 300, 'badge-warning': mychallenges['ping'] && mychallenges['ping'].recent >= 300 && mychallenges['ping'].recent < 900, 'badge-danger': mychallenges['ping'] && mychallenges['ping'].recent >= 900, 'badge-dark': !mychallenges['ping']}" title="{{ mychallenges['ping'].time }}">
&#x1f4bb; &#x1f4bb;
@ -116,7 +116,7 @@
<a class="text-dark" href="/dashboard/{{login}}">{{ login }}</a> <a class="text-dark" href="/dashboard/{{login}}">{{ login }}</a>
</h5> </h5>
<div class="d-flex flex-wrap justify-content-around" style="padding: 0"> <div class="d-flex flex-wrap justify-content-around" style="padding: 0">
<span class="badge" style="margin-left: .07rem" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch]}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span> <span class="badge badge-sm" style="margin-left: .07rem" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch] || !mychallenges[ch].recent}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.icon"></span>
</div> </div>
</div> </div>
</div> </div>
@ -126,6 +126,7 @@
<script src="js/angular-resource.min.js"></script> <script src="js/angular-resource.min.js"></script>
<script src="js/angular-route.min.js"></script> <script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script> <script src="js/angular-sanitize.min.js"></script>
<script src="js/adlin-common.js"></script>
<script src="js/adlin-dashboard.js"></script> <script src="js/adlin-dashboard.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,28 @@
var tuto_progress = [
{
1: { title: "Is alive?", icon: "👋", label: "Token 1"},
2: { title: "DMZ reached", icon: "📚", label: "Token 2"},
3: { title: "HTTPS on + time", icon: "⏲", label: "Token 3"},
4: { title: "DNS ok", icon: "🍰", label: "Token 4"},
5: { title: "On Internet", icon: "🌎", label: "Token 5"},
6: { title: "Bonus caché", icon: "b", label: "Bonus 0"},
7: { title: "Bonus ICMP", icon: "🏓", label: "Bonus 1"},
8: { title: "Bonus disk", icon: "💽", label: "Bonus 2"},
9: { title: "Bonus email", icon: "📧", label: "Bonus 3"},
10: { title: "Wg tunnel", icon: "🚇", label: "Tunnel up"},
11: { title: "Uploaded SSH key", icon: "💊", label: "SSH"},
},
{
100: { title: "HTTP on IP", icon: "0", label: "HTTP IP"},
101: { title: "HTTP on associated domain", icon: "1", label: "HTTP domain"},
102: { title: "HTTPS on associated domain", icon: "2", label: "HTTPS domain"},
103: { title: "DNS Delegation", icon: "3", label: "DNS"},
104: { title: "HTTP on delegated domain", icon: "4", label: "HTTP on NS"},
105: { title: "HTTPS on delegated domain", icon: "5", label: "HTTPS on NS"},
106: { title: "Matrix", icon: "6", label: "Matrix"},
107: { title: "DNSSEC (bonus)", icon: "7", label: "DNSSEC"},
},
{
200: { title: "HTTP", label: "HTTP"},
},
];

View file

@ -1,32 +1,3 @@
var tuto_progress = [
{
1: { title: "Is alive?", label: "👋"},
2: { title: "DMZ reached", label: "📚"},
3: { title: "HTTPS on + time", label: "⏲"},
4: { title: "DNS ok", label: "🍰"},
5: { title: "On Internet", label: "🌎"},
6: { title: "Bonus caché", label: "b"},
7: { title: "Bonus ICMP", label: "🏓"},
8: { title: "Bonus disk", label: "💽"},
9: { title: "Bonus email", label: "📧"},
10: { title: "Wg tunnel", label: "🚇"},
11: { title: "Uploaded SSH key", label: "💊"},
},
{
100: { title: "HTTP", label: "HTTP"},
101: { title: "HTTPS", label: "HTTPS"},
102: { title: "DNS", label: "DNS"},
103: { title: "Matrix", label: "Matrix"},
},
{
200: { title: "HTTP", label: "HTTP"},
201: { title: "HTTPS", label: "HTTPS"},
202: { title: "DNS", label: "DNS"},
203: { title: "Matrix", label: "Matrix"},
},
];
angular.module("AdLinApp", ["ngResource", "ngSanitize"]) angular.module("AdLinApp", ["ngResource", "ngSanitize"])
.factory("Student", function($resource) { .factory("Student", function($resource) {
return $resource("/api/students/:studentId", { studentId: '@id' }, { return $resource("/api/students/:studentId", { studentId: '@id' }, {
@ -65,7 +36,8 @@ angular.module("AdLinApp")
var refreshStd = function() { var refreshStd = function() {
$scope.students = Student.query(); $scope.students = Student.query();
} }
$interval(refreshStd, 1600000); var myinterval = $interval(refreshStd, 1600000);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })
.controller("StudentsProgressionController", function($scope, $interval, Progression) { .controller("StudentsProgressionController", function($scope, $interval, Progression) {
$scope.tuto_progress = tuto_progress; $scope.tuto_progress = tuto_progress;
@ -109,7 +81,8 @@ angular.module("AdLinApp")
}) })
} }
refreshStd(); refreshStd();
$interval(refreshStd, 9750); var myinterval = $interval(refreshStd, 9750);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })
.controller("StudentProgressionController", function($scope, $interval, $http, Student, StudentProgression) { .controller("StudentProgressionController", function($scope, $interval, $http, Student, StudentProgression) {
$scope.tuto_progress = tuto_progress; $scope.tuto_progress = tuto_progress;
@ -133,7 +106,8 @@ angular.module("AdLinApp")
} }
$scope.$watch("onestudent", function(onestudent) { $scope.$watch("onestudent", function(onestudent) {
refreshStd(); refreshStd();
$interval(refreshStd, 15000); var myinterval = $interval(refreshStd, 15000);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })
}) })
.controller("PingController", function($scope, $interval, $http) { .controller("PingController", function($scope, $interval, $http) {
@ -150,7 +124,8 @@ angular.module("AdLinApp")
$scope.$watch("student", function(student) { $scope.$watch("student", function(student) {
student.$promise.then(function(std) { student.$promise.then(function(std) {
refreshPing(); refreshPing();
$interval(refreshPing, 15000); var myinterval = $interval(refreshPing, 15000);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })
}) })
}) })
@ -162,7 +137,8 @@ angular.module("AdLinApp")
}); });
} }
refreshSSH(); refreshSSH();
$interval(refreshSSH, 15500); var myinterval = $interval(refreshSSH, 15500);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })
.controller("ProgressionController", function($scope, $interval, $http) { .controller("ProgressionController", function($scope, $interval, $http) {
$scope.tuto_progress = tuto_progress; $scope.tuto_progress = tuto_progress;
@ -179,5 +155,6 @@ angular.module("AdLinApp")
}); });
} }
refreshChal(); refreshChal();
$interval(refreshChal, 15750); var myinterval = $interval(refreshChal, 15750);
$scope.$on('$destroy', function () { $interval.cancel(myinterval); });
}) })

View file

@ -44,6 +44,17 @@ angular.module("AdLinApp")
} }
}) })
.directive('integer', function() {
return {
require: 'ngModel',
link: function(scope, ele, attr, ctrl){
ctrl.$parsers.unshift(function(viewValue){
return parseInt(viewValue, 10);
});
}
};
})
.component('toast', { .component('toast', {
bindings: { bindings: {
date: '=', date: '=',
@ -100,6 +111,8 @@ angular.module("AdLinApp")
}) })
.controller("ProgressionController", function($scope, $interval, $http) { .controller("ProgressionController", function($scope, $interval, $http) {
$scope.tuto_progress = tuto_progress;
$scope.mychallenges = {}; $scope.mychallenges = {};
var refreshChal = function() { var refreshChal = function() {
$http.get("api/students/" + $scope.student.id + "/progress").then(function(response) { $http.get("api/students/" + $scope.student.id + "/progress").then(function(response) {
@ -150,7 +163,10 @@ angular.module("AdLinApp")
}; };
$scope.updateTunnelInfo(); $scope.updateTunnelInfo();
$scope.updateTunnelsList = function() { var noUpdate = 0
$scope.updateTunnelsList = function() {
if (noUpdate == 0)
$http({ $http({
method: 'GET', method: 'GET',
url: "api/wg/", url: "api/wg/",
@ -211,11 +227,48 @@ angular.module("AdLinApp")
}); });
} }
$scope.editTunnel = function(tunnel) {
tunnel.edit = true;
noUpdate++;
tunnel.newData = {
TokenText: tunnel.TokenText,
SuffixIP: tunnel.SuffixIP,
}
};
$scope.updateTunnel = function(tunnel) {
tunnel.pleaseWaitUpdate = true;
$http({
method: 'PUT',
url: "api/wg/" + encodeURIComponent(tunnel.TokenText),
data: tunnel.newData
}).then(function(response) {
noUpdate--;
tunnel.SuffixIP = tunnel.newData.SuffixIP;
tunnel.TokenText = tunnel.newData.TokenText;
tunnel.edit = false;
tunnel.pleaseWaitUpdate = false;
$scope.updateTunnelsList();
$scope.addToast({
variant: "success",
title: "Maatma Tunnels",
msg: "Tunnel mise à jour avec succès !",
});
}, function(response) {
tunnel.pleaseWaitUpdate = false;
$scope.addToast({
variant: "danger",
title: "Maatma Tunnels",
msg: (response.data ? response.data.errmsg : "Impossible de contacter le serveur"),
});
});
}
$scope.dropTunnel = function(tunnel) { $scope.dropTunnel = function(tunnel) {
tunnel.pleaseWaitDrop = true; tunnel.pleaseWaitDrop = true;
$http({ $http({
method: 'DELETE', method: 'DELETE',
url: "api/wg/" + tunnel.TokenText, url: "api/wg/" + encodeURIComponent(tunnel.TokenText),
data: {} data: {}
}).then(function(response) { }).then(function(response) {
$scope.updateTunnelsList(); $scope.updateTunnelsList();
@ -330,6 +383,123 @@ angular.module("AdLinApp")
}); });
} }
$scope.useMyAssociationD = function() {
$scope.assoc = {
"domain": $scope.adomains[0].domain,
"cname": $scope.student.associated_domain?$scope.student.associated_domain:"",
}
$('#AssocMyDomainModal').modal('show');
}
$scope.newMyDomainAssociationD = function(assoc) {
$('#AssocMyDomainModal').modal('hide');
$scope.pleaseWaitNewAssociation = true;
$http({
method: 'POST',
url: "api/adomains/",
data: assoc,
}).then(function(response) {
$scope.updateAssociationD();
$scope.checkLoginState();
$scope.pleaseWaitNewAssociation = false;
$scope.addToast({
variant: "success",
title: "Maatma Domain Names",
msg: "Votre domaine a bien été associé !",
});
}, function(response) {
$scope.pleaseWaitNewAssociation = false;
$scope.addToast({
variant: "danger",
title: "Maatma Domain Names",
msg: "Erreur durant l'association du domaine : " + response.data.errmsg,
});
});
}
$scope.delMyDomainAssociationD = function(assoc) {
$('#AssocMyDomainModal').modal('hide');
$scope.pleaseWaitNewAssociation = true;
assoc.cname = ''
$http({
method: 'POST',
url: "api/adomains/",
data: assoc,
}).then(function(response) {
$scope.updateAssociationD();
$scope.checkLoginState();
$scope.pleaseWaitNewAssociation = false;
$scope.addToast({
variant: "success",
title: "Maatma Domain Names",
msg: "Votre domaine n'est plus pris en compte. Vous devez utiliser l'association qui vous a été attribuée sous adlin20xx.p0m.fr.",
});
}, function(response) {
$scope.pleaseWaitNewAssociation = false;
$scope.addToast({
variant: "danger",
title: "Maatma Domain Names",
msg: "Erreur durant l'association du domaine : " + response.data.errmsg,
});
});
}
$scope.useMyDelegationD = function() {
$scope.assoc = {
"ns": $scope.student.delegated_domain?$scope.student.delegated_domain:"",
}
$('#DelegateMyDomainModal').modal('show');
}
$scope.newMyDomainDelegationD = function(assoc) {
$('#DelegateMyDomainModal').modal('hide');
$scope.pleaseWaitNewDelegation = true;
$http({
method: 'POST',
url: "api/ddomains/",
data: assoc,
}).then(function(response) {
$scope.checkLoginState();
$scope.pleaseWaitNewDelegation = false;
$scope.addToast({
variant: "success",
title: "Maatma Domain Names",
msg: "Votre sous-domaine de délégation a bien été enregistré !",
});
}, function(response) {
$scope.pleaseWaitNewDelegation = false;
$scope.addToast({
variant: "danger",
title: "Maatma Domain Names",
msg: "Erreur durant la délégation du domaine : " + response.data.errmsg,
});
});
}
$scope.delMyDomainDelegatedD = function() {
$scope.pleaseWaitNewDelegation = true;
$http({
method: 'POST',
url: "api/ddomains/",
data: {},
}).then(function(response) {
$scope.checkLoginState();
$scope.pleaseWaitNewDelegation = false;
$scope.addToast({
variant: "success",
title: "Maatma Domain Names",
msg: "Votre domaine n'est plus pris en compte. Vous devez utiliser la délégation qui vous a été attribuée sous srs.p0m.fr.",
});
}, function(response) {
$scope.pleaseWaitNewDelegation = false;
$scope.addToast({
variant: "danger",
title: "Maatma Domain Names",
msg: "Erreur durant la délégation du domaine : " + response.data.errmsg,
});
});
}
$scope.addNS = function(domain) { $scope.addNS = function(domain) {
$scope.nsrr = { $scope.nsrr = {
"domain": domain, "domain": domain,

View file

@ -46,6 +46,7 @@
<script src="js/angular-resource.min.js"></script> <script src="js/angular-resource.min.js"></script>
<script src="js/angular-route.min.js"></script> <script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script> <script src="js/angular-sanitize.min.js"></script>
<script src="js/adlin-common.js"></script>
<script src="js/adlin-main.js"></script> <script src="js/adlin-main.js"></script>
</body> </body>
</html> </html>

View file

@ -2,7 +2,10 @@
Noms de domaine Noms de domaine
</h2> </h2>
<h3>Association simple</h3> <h3>
Association simple
<span class="badge badge-pill badge-dark" title="Une association simple est un sous-domaine qui va vous être attribué, sans que vous ayez à gérer de serveur DNS. Ce domaine sera associé à une IP que vous pourrez changer par la suite. C'est la première étape, plus facile que la délégation.">?</span>
</h3>
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
@ -28,6 +31,9 @@
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewAssociation"></span> <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewAssociation"></span>
Demander une nouvelle association Demander une nouvelle association
</button> </button>
<button class="btn ml-2" ng-class="{'btn-secondary': !adomains || !adomains.length, 'btn-success': adomains && adomains.length}" ng-disabled="!adomains || !adomains.length" ng-click="useMyAssociationD()">
Utiliser mon domaine
</button>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
@ -35,7 +41,10 @@
<hr class="my-4"> <hr class="my-4">
<h3>Délégation</h3> <h3>
Délégation
<span class="badge badge-pill badge-dark" title="Une délégation va vous permettre de gérer vous-même votre domaine sur Internet. Nous vous offrons login.srs.p0m.fr, mais vous pouvez aussi choisir de créer la délégation sur votre domaine, si vous en possédez un. Rendez-vous ensuite sur votre serveur pour y installer un serveur de noms de domaine autoritaire tel que nsd, bind, knot ou encore powerDNS...">?</span>
</h3>
<ul class="nav nav-tabs" id="ddomainTabs" role="tablist"> <ul class="nav nav-tabs" id="ddomainTabs" role="tablist">
<li class="nav-item"> <li class="nav-item">
@ -48,11 +57,17 @@
<a class="nav-link" data-toggle="tab" data-target="#DNSSEC" role="tab" aria-controls="dnssec" aria-selected="false">DNSSEC</a> <a class="nav-link" data-toggle="tab" data-target="#DNSSEC" role="tab" aria-controls="dnssec" aria-selected="false">DNSSEC</a>
</li> </li>
</ul> </ul>
<div class="tab-content" id="myTabContent"> <div ng-if="!student.delegated_domain" class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="NS" role="tabpanel" aria-labelledby="ns-tab"> <div class="tab-pane fade show active" id="NS" role="tabpanel" aria-labelledby="ns-tab">
<div ng-repeat="domain in ddomains"> <div ng-repeat="domain in ddomains">
<h4 class="text-muted">{{ domain }}</h4> <h4 class="text-muted">
{{ domain }}
<button class="btn btn-sm btn-info ml-2" ng-if="$first" ng-click="useMyDelegationD()">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewDelegation"></span>
Utiliser mon domaine
</button>
</h4>
<table class="table table-striped table-hover" ng-controller="NSDomainsController"> <table class="table table-striped table-hover" ng-controller="NSDomainsController">
<thead> <thead>
@ -65,7 +80,7 @@
<tr ng-repeat="rr in domainNS"> <tr ng-repeat="rr in domainNS">
<td><span ng-repeat="val in rr.values">{{ val }} </span></td> <td><span ng-repeat="val in rr.values">{{ val }} </span></td>
<td> <td>
<span class="badge badge-pill badge-secondary">Not implemented yet</span> <!--span class="badge badge-pill badge-secondary">Not implemented yet</span-->
</td> </td>
<td> <td>
<button class="btn btn-warning" ng-click="updateNS(domain, rr)">Modifier</button> <button class="btn btn-warning" ng-click="updateNS(domain, rr)">Modifier</button>
@ -180,7 +195,31 @@
</div> </div>
</div> </div>
<div ng-if="student.delegated_domain">
<h4 class="text-muted">
{{ student.delegated_domain }}
<button class="btn btn-sm btn-danger ml-2" ng-if="student.delegated_domain" ng-click="delMyDomainDelegatedD()">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewDelegation"></span>
Ne plus utiliser mon domaine
</button>
</h4>
<p>
Vous avez choisi d'utiliser votre propre domaine pour réaliser la délégation.
</p>
<p>
L'interface de maatma ne vous est plus utile, car pour réaliser la délégation, vous devez passer par l'interface de votre bureau d'enregistrement.
</p>
<p>
Pour rappel, voici les enregistrements à rajouter&nbsp;:
</p>
<pre>
;; Delegation {{ student.delegated_domain }} to the given name server
{{ student.delegated_domain }} 300 IN NS ns.{{ student.delegated_domain }}
;; GLUE record to serve along with the previous record
ns.{{ student.delegated_domain }} 300 IN AAAA [your NS ip]
</pre>
</div>
<div class="modal" id="AssocModal" tabindex="-1" role="dialog"> <div class="modal" id="AssocModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
@ -224,6 +263,85 @@
</div> </div>
</div> </div>
<div class="modal" id="AssocMyDomainModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Utiliser mon domaine</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
Si tu possèdes ton propre domaine, tu peux l'utiliser à la place du domaine qui t'es proposé ici&nbsp;!
</p>
<p>
Pour cela, rien de plus simple. Choisis un sous-domaine de ton choix, dans ton domaine (par exemple <code>adlin.nemunai.re</code>)&nbsp;:
</p>
<form class="ml-2 mr-2">
<div class="form-group">
<label class="form-label" for="mysubdomain">Ton sous-domaine&nbsp;:</label>
<input class="form-control" id="mysubdomain" ng-model="assoc.cname" autofocus>
</div>
</form>
<p>
Ensuite, dans ta zone DNS, ajoute un alias pointant vers le domaine sous <code>adlin20xx.p0m.fr</code> qui t'es proposé&nbsp;:
</p>
<pre>
{{ assoc.cname }} 300 IN CNAME {{ assoc.domain }}
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" ng-disabled="!assoc.cname" ng-click="newMyDomainAssociationD(assoc)">Ok, c'est fait</button>
<button type="button" class="btn btn-danger" ng-show="student.associated_domain" ng-click="delMyDomainAssociationD(assoc)">Supprimer l'association enregistrée</button>
</div>
</div>
</div>
</div>
<div class="modal" id="DelegateMyDomainModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Utiliser mon domaine comme délégation</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
Tu peux utiliser ton propre domaine à la place du domaine qui t'es proposé ici&nbsp;!
</p>
<p>
Commence par choisir un sous-domaine de ton choix (différent de l'association ci-dessus), dans ton domaine (par exemple <code>adlin.nemunai.re</code>)&nbsp;:
</p>
<form class="ml-2 mr-2">
<div class="form-group">
<label class="form-label" for="mysubdomaind">Ton sous-domaine&nbsp;:</label>
<input class="form-control" id="mysubdomaind" ng-model="assoc.ns" autofocus>
</div>
</form>
<p>
Ensuite, configure ta zone DNS, pour réaliser la délégation. L'interface
</p>
<pre>
;; Delegation {{ assoc.ns }} to the given name server
{{ assoc.ns }} 300 IN NS ns.{{ assoc.ns }}
;; GLUE record to serve along with the previous record
ns.{{ assoc.ns }} 300 IN AAAA [your NS ip]
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" ng-disabled="!assoc.ns" ng-click="newMyDomainDelegationD(assoc)">Ok, c'est fait</button>
</div>
</div>
</div>
</div>
<div class="modal" id="NSModal" tabindex="-1" role="dialog"> <div class="modal" id="NSModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">

View file

@ -10,26 +10,31 @@
Connectez-vous&nbsp;! Connectez-vous&nbsp;!
</h2> </h2>
<h2 ng-if="isLogged"> <h2 ng-if="isLogged" class="mb-4">
Qu'allons-nous faire aujourd'hui&nbsp;? <a href="/dashboard/{{ isLogged.login }}" class="text-muted">Voir l'avancement...</a> Qu'allons-nous faire aujourd'hui&nbsp;? <a href="/dashboard/{{ isLogged.login }}" class="text-muted">Voir l'avancement...</a>
</h2> </h2>
<div ng-controller="ProgressionController" class="row" ng-if="isLogged"> <div ng-controller="ProgressionController" ng-if="isLogged">
<div class="col"> <div ng-repeat="(tutoid,tuto) in tuto_progress" class="mb-2 row">
<strong>TP 2&nbsp;</strong> <strong class="col-auto mr-2">TP {{tutoid+1}}</strong>
<span class="badge" ng-class="{'badge-success': mychallenges[100], 'badge-danger': !mychallenges[100]}">HTTP</span> <div class="col">
<span class="badge" ng-class="{'badge-success': mychallenges[101], 'badge-danger': !mychallenges[101]}">HTTPS</span> <div class="ml-1 align-items-center" ng-class="{'d-inline-block': !mychallenges[ch] || !mychallenges[ch].error || mychallenges[ch].error == 'OK', 'd-flex': !(!mychallenges[ch] || !mychallenges[ch].error || mychallenges[ch].error == 'OK')}" ng-repeat="(ch,t) in tuto_progress[tutoid]">
<span class="badge" ng-class="{'badge-success': mychallenges[102], 'badge-danger': !mychallenges[102]}">DNS</span> <span class="badge mr-1" ng-class="{'badge-success': mychallenges[ch] && (!mychallenges[ch].error || mychallenges[ch].error == 'OK'), 'badge-warning': mychallenges[ch] && mychallenges[ch].error && mychallenges[ch].error != 'OK', 'badge-danger': !mychallenges[ch] || !mychallenges[ch].time}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
<span class="badge" ng-class="{'badge-success': mychallenges[103], 'badge-danger': !mychallenges[103]}">Matrix</span> <pre class="ml-1 mb-0" ng-show="mychallenges[ch] && mychallenges[ch].error && mychallenges[ch].error != 'OK'">{{ mychallenges[ch].error }}</pre>
</div> </div>
<div class="col"> </div>
<strong>TP 3&nbsp;</strong>
<span class="badge" ng-class="{'badge-success': mychallenges[200], 'badge-danger': !mychallenges[200]}">HTTP</span>
<span class="badge" ng-class="{'badge-success': mychallenges[201], 'badge-danger': !mychallenges[201]}">HTTPS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[202], 'badge-danger': !mychallenges[202]}">DNS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[203], 'badge-danger': !mychallenges[203]}">Matrix</span>
</div> </div>
</div> </div>
<h2 class="mt-4 mb-2" ng-if="isLogged">
Besoin d'aide&nbsp;?
</h2>
<p ng-if="isLogged">
Rejoignez-nous sur notre canal
<a href="https://matrix.to/#/#maatma:nemunai.re" target="_blank"><img src="https://matrix.to/images/matrix-logo-3303998978.svg" alt="Rejoingnez-nous sur notre canal Matrix"></a>
</p>
</div> </div>
<div class="card-deck"> <div class="card-deck">

View file

@ -6,22 +6,47 @@
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>Token</th> <th>
<th>Dernière utilisation</th> Token
<th>Clef publique</th> <span class="badge badge-pill badge-dark" title="Utilisez l'un de vos tokens pour connecter votre machine virtuelle à un réseau IPv6 routable sur Internet. Ce token vous sera demandé au premier démarrage de la VM.">?</span>
</th>
<th>
Suffix
<span class="badge badge-pill badge-dark" title="Si vous souhaitez pouvoir utiliser plusieurs tunnels en parallèle, tout en bénéficiant de la mise en place automatique du tunnel au démarrage, vous allez devoir vous définir un suffix. Il s'agit de la dernière partie de l'adresse qui sera attribué à votre machine.">?</span>
</th>
<th>
Dernière utilisation
<span class="badge badge-pill badge-dark" title="Si vous constatez une utilisation frauduleuse, révoquez votre tunnel.">?</span>
</th>
<th>
Clef publique
<span class="badge badge-pill badge-dark" title="La clef publique est automatiquement envoyée par votre machine virtuelle lorsqu'elle valide le token. C'est une clef cryptographique qui est utiliser pour chiffrer le canal VPN utilisé.">?</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="tunnel in tunnels" ng-class="{'bg-success': tunnel.dump}"> <tr ng-repeat="tunnel in tunnels" ng-class="{'bg-success': tunnel.dump}">
<td> <td>
<span ng-if="tunnel.Dump" class="text-bold text-success">&gt;</span> <span ng-if="tunnel.Dump" title="Tunnel actif (voir information ci-dessous)" class="font-weight-bold text-success">&gt;</span>
<span ng-if="!tunnel.Dump">&#x274c;</span> <span ng-if="!tunnel.Dump" title="Tunnel inactif">&#x274c;</span>
</td> </td>
<td><code>{{ tunnel.TokenText }}</code></td> <td><code>{{ tunnel.TokenText }}</code></td>
<td ng-if="tunnel.edit">
<input class="form-control" ng-model="tunnel.newData.SuffixIP" placeholder="Suffixe d'IP par défaut" autofocus integer>
</td>
<td ng-if="!tunnel.edit && tunnel.SuffixIP">{{ tunnel.SuffixIP }}</td>
<td ng-if="!tunnel.edit && !tunnel.SuffixIP">Par défaut</td>
<td>{{ tunnel.Time | date:"medium" }}<span ng-if="tunnel.Version"> (VM TP {{ tunnel.Version }})</span></td> <td>{{ tunnel.Time | date:"medium" }}<span ng-if="tunnel.Version"> (VM TP {{ tunnel.Version }})</span></td>
<td><code ng-show="tunnel.PubKey">{{ tunnel.PubKey }}</code><span ng-show="!tunnel.PubKey">(none)</span></td> <td><code class="text-truncate" title="{{ tunnel.PubKey }}" ng-show="tunnel.PubKey">{{ tunnel.PubKey }}</code><span ng-show="!tunnel.PubKey">(none)</span></td>
<td> <td>
<button class="btn btn-sm btn-danger" ng-click="dropTunnel(tunnel)" disabled> <button class="btn btn-sm btn-info" ng-click="editTunnel(tunnel)" ng-if="!tunnel.edit">
Éditer
</button>
<button class="btn btn-sm btn-success" ng-click="updateTunnel(tunnel)" ng-if="tunnel.edit">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="tunnel.pleaseWaitUpdate"></span>
Enregistrer
</button>
<button class="btn btn-sm btn-danger" ng-click="dropTunnel(tunnel)">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="tunnel.pleaseWaitDrop"></span> <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="tunnel.pleaseWaitDrop"></span>
Révoquer Révoquer
</button> </button>
@ -42,7 +67,10 @@
<div class="card-deck mb-4"> <div class="card-deck mb-4">
<div class="card"> <div class="card">
<h4 class="card-header">Paramètres du tunnel</h4> <h4 class="card-header">
Paramètres du tunnel
<span class="float-right badge badge-pill badge-dark" title="Ce cadre vous montre les informations sur le routeur hébergeant le tunnel chez Maatma. Vérifiez notamment que vous avez les mêmes paramètres dans vos machines.">?</span>
</h4>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Statut&nbsp;:</strong> {{ wginfo.status }}</li> <li class="list-group-item"><strong>Statut&nbsp;:</strong> {{ wginfo.status }}</li>
<li class="list-group-item"><strong>Clef publique du serveur&nbsp;:</strong> <code>{{ wginfo.srv_pubkey }}</code></li> <li class="list-group-item"><strong>Clef publique du serveur&nbsp;:</strong> <code>{{ wginfo.srv_pubkey }}</code></li>
@ -51,10 +79,11 @@
<li class="list-group-item"><strong>Gateway/passerelle IPv6&nbsp;:</strong> {{ wginfo.srv_gw6 }}</li> <li class="list-group-item"><strong>Gateway/passerelle IPv6&nbsp;:</strong> {{ wginfo.srv_gw6 }}</li>
</ul> </ul>
</div> </div>
<div class="card" ng-repeat="tunnel in tunnels" ng-if="tunnel.Dump"> <div class="card mb-2" ng-repeat="tunnel in tunnels" ng-if="tunnel.Dump">
<h4 class="card-header"> <h4 class="card-header">
État de mon tunnel État de mon tunnel
<span class="badge" ng-class="{'badge-success': PING && PING < 90, 'badge-danger': !PING || PING >= 90}" title="{{ PING_time }}">&#x1f4bb;</span> <span class="badge" ng-if="$first" ng-class="{'badge-success': PING && PING < 90, 'badge-danger': !PING || PING >= 90}" title="{{ PING_time }}">&#x1f4bb;</span>
<span class="float-right badge badge-pill badge-dark" title="Voici les informations concernant l'un de vos tunnels actuellement actif (pas forcément connecté, reportez-vous à l'icône pour savoir cela).">?</span>
</h4> </h4>
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Clef publique pair&nbsp;:</strong> <code>{{ tunnel.Dump.PubKey }}</code></li> <li class="list-group-item"><strong>Clef publique pair&nbsp;:</strong> <code>{{ tunnel.Dump.PubKey }}</code></li>

View file

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"net" "net"
"strconv"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
@ -11,12 +12,12 @@ import (
func init() { func init() {
router.GET("/api/ips", apiHandler(showIPs)) router.GET("/api/ips", apiHandler(showIPs))
router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student adlin.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 return getStudentIPs(student), nil
}))) })))
} }
func IPSuffix(s adlin.Student, network net.IPNet) net.IP { func IPSuffix(s *adlin.Student, network net.IPNet) net.IP {
ipshift := s.Id*4 + 10 ipshift := s.Id*4 + 10
myIP := network.IP myIP := network.IP
@ -54,13 +55,32 @@ func showIPs(_ httprouter.Params, body []byte) (interface{}, error) {
return r, nil return r, nil
} }
func getStudentIPs(student adlin.Student) (r map[string]string) { func GetStudentTunnelIPs(student *adlin.Student) (ips []string) {
if ts, err := student.GetActivesTunnels(); err != nil || len(ts) == 0 || ts[0].SuffixIP == 0 {
ips = append(ips, adlin.StudentIP(student.Id).String()+"1")
} else {
for _, t := range ts {
ips = append(ips, t.GetStudentIP())
}
}
return
}
func getStudentIPs(student *adlin.Student) (r map[string]string) {
r = make(map[string]string) r = make(map[string]string)
r["vlan0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.0.0"), net.CIDRMask(17, 32)}).String() r["vlan0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.0.0"), net.CIDRMask(17, 32)}).String()
r["wg0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.17.0.0"), net.CIDRMask(16, 32)}).String() r["wg0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.17.0.0"), net.CIDRMask(16, 32)}).String()
r["vlan7"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.142.0"), net.CIDRMask(23, 32)}).String() r["vlan7"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.142.0"), net.CIDRMask(23, 32)}).String()
r["wg"] = adlin.StudentIP(student.Id).String()
for d, ip := range GetStudentTunnelIPs(student) {
key := "wg"
if d > 0 {
key += strconv.Itoa(d)
}
r[key] = ip
}
r["adn"] = student.MyAssociatedDomain() r["adn"] = student.MyAssociatedDomain()
r["ddn"] = student.MyDelegatedDomain() r["ddn"] = student.MyDelegatedDomain()

View file

@ -11,13 +11,13 @@ var PongSecret = "felixfixit"
func init() { func init() {
router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing))) router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing)))
router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) { router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student *adlin.Student, body []byte) (interface{}, error) {
return student.LastPongs() return student.LastPongs()
}))) })))
router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly)) router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly))
} }
func lastPing(student adlin.Student, body []byte) (interface{}, error) { func lastPing(student *adlin.Student, body []byte) (interface{}, error) {
if pongs, err := student.LastPongs(); err != nil { if pongs, err := student.LastPongs(); err != nil {
return nil, err return nil, err
} else if len(pongs) <= 0 { } else if len(pongs) <= 0 {
@ -27,7 +27,7 @@ func lastPing(student adlin.Student, body []byte) (interface{}, error) {
} }
} }
func stdPong(student adlin.Student, body []byte) (interface{}, error) { func stdPong(student *adlin.Student, body []byte) (interface{}, error) {
var gt givenToken var gt givenToken
if err := json.Unmarshal(body, &gt); err != nil { if err := json.Unmarshal(body, &gt); err != nil {
return nil, err return nil, err

View file

@ -47,7 +47,7 @@ func init() {
}) })
} }
func hasSSHKeys(student adlin.Student, body []byte) (interface{}, error) { func hasSSHKeys(student *adlin.Student, body []byte) (interface{}, error) {
if keys, err := student.GetKeys(); err != nil { if keys, err := student.GetKeys(); err != nil {
return nil, err return nil, err
} else { } else {
@ -141,7 +141,7 @@ func dumpAuthorizedKeysFile(w io.Writer) {
} }
} }
func dumpStdAuthorizedKeysFile(s adlin.Student, w io.Writer) { func dumpStdAuthorizedKeysFile(s *adlin.Student, w io.Writer) {
seen := map[string]interface{}{} seen := map[string]interface{}{}
if keys, _ := s.GetKeys(); keys != nil { if keys, _ := s.GetKeys(); keys != nil {

View file

@ -18,19 +18,19 @@ func init() {
if stds, err := adlin.GetStudents(); err != nil { if stds, err := adlin.GetStudents(); err != nil {
return nil, err return nil, err
} else { } else {
ret := map[string]map[string]adlin.UnlockedChallenge{} ret := map[string]map[string]*adlin.UnlockedChallenge{}
for _, std := range stds { for _, std := range stds {
if sts, err := std.GetStates(); err == nil { if sts, err := std.GetStates(); err == nil {
ret[std.Login] = map[string]adlin.UnlockedChallenge{} ret[std.Login] = map[string]*adlin.UnlockedChallenge{}
for _, s := range sts { for _, s := range sts {
ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s
} }
if pongs, err := std.LastPongs(); err == nil && len(pongs) > 0 { if pongs, err := std.LastPongs(); err == nil && len(pongs) > 0 {
ret[std.Login]["ping"] = adlin.UnlockedChallenge{ ret[std.Login]["ping"] = &adlin.UnlockedChallenge{
IdStudent: std.Id, IdStudent: std.Id,
Time: pongs[0].Date, Time: &pongs[0].Date,
Value: pongs[0].State, Value: pongs[0].State,
} }
} else if err != nil { } else if err != nil {
@ -47,17 +47,17 @@ func init() {
})) }))
router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent))) router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent)))
router.GET("/api/students/:sid/", apiHandler(studentHandler( router.GET("/api/students/:sid/", apiHandler(studentHandler(
func(std adlin.Student, _ []byte) (interface{}, error) { func(std *adlin.Student, _ []byte) (interface{}, error) {
return std, nil return std, nil
}))) })))
router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent)))) router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent))))
router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler( router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(
func(std adlin.Student, _ []byte) (interface{}, error) { func(std *adlin.Student, _ []byte) (interface{}, error) {
return std.Delete() return std.Delete()
})))) }))))
router.GET("/api/students/:sid/progress", apiHandler(studentHandler( router.GET("/api/students/:sid/progress", apiHandler(studentHandler(
func(std adlin.Student, _ []byte) (interface{}, error) { func(std *adlin.Student, _ []byte) (interface{}, error) {
ret := map[string]adlin.UnlockedChallenge{} ret := map[string]*adlin.UnlockedChallenge{}
if sts, err := std.GetStates(); err == nil { if sts, err := std.GetStates(); err == nil {
for _, s := range sts { for _, s := range sts {
@ -65,6 +65,20 @@ func init() {
} }
} }
if cerrors, err := std.GetChallengeErrors(); err == nil {
for _, cerr := range cerrors {
if _, ok := ret[fmt.Sprintf("%d", cerr.Challenge)]; ok {
ret[fmt.Sprintf("%d", cerr.Challenge)].Error = cerr.Error
ret[fmt.Sprintf("%d", cerr.Challenge)].LastCheck = &cerr.Time
} else {
ret[fmt.Sprintf("%d", cerr.Challenge)] = &adlin.UnlockedChallenge{
LastCheck: &cerr.Time,
Error: cerr.Error,
}
}
}
}
return ret, nil return ret, nil
}))) })))
} }
@ -82,7 +96,7 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
return nil, err return nil, err
} }
var exist adlin.Student var exist *adlin.Student
if exist, err = adlin.GetStudentByLogin(strings.TrimSpace(std.Login)); err != nil { if exist, err = adlin.GetStudentByLogin(strings.TrimSpace(std.Login)); err != nil {
if exist, err = adlin.NewStudent(strings.TrimSpace(std.Login)); err != nil { if exist, err = adlin.NewStudent(strings.TrimSpace(std.Login)); err != nil {
return nil, err return nil, err
@ -96,8 +110,8 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
return exist, nil return exist, nil
} }
func updateStudent(current adlin.Student, body []byte) (interface{}, error) { func updateStudent(current *adlin.Student, body []byte) (interface{}, error) {
var new adlin.Student new := &adlin.Student{}
if err := json.Unmarshal(body, &new); err != nil { if err := json.Unmarshal(body, &new); err != nil {
return nil, err return nil, err
} }

View file

@ -27,20 +27,22 @@ func init() {
} }
}) })
router.GET("/api/wg/", apiAuthHandler(showWgTunnel)) router.GET("/api/wg/", apiAuthHandler(showWgTunnel))
router.GET("/api/wginfo", apiAuthHandler(func(student adlin.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 return getTunnelInfo(student.Id), nil
})) }))
router.POST("/api/wg/", apiAuthHandler(genWgToken)) router.POST("/api/wg/", apiAuthHandler(genWgToken))
router.GET("/api/wg/:token", getWgTunnelInfo) router.GET("/api/wg/:token", getWgTunnelInfo)
router.POST("/api/wg/:token", getWgTunnelInfo) router.POST("/api/wg/:token", getWgTunnelInfo)
router.PUT("/api/wg/:token", apiAuthHandler(updateWgTunnel))
router.DELETE("/api/wg/:token", apiAuthHandler(deleteWgTunnel))
} }
func showWgTunnel(student adlin.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 // Get tunnels assigned to the student
return student.GetTunnelTokens() return student.GetTunnelTokens()
} }
func genWgToken(student adlin.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 // Generate a token to access related wg info
return student.NewTunnelToken(0) return student.NewTunnelToken(0)
} }
@ -61,7 +63,7 @@ func getTunnelInfo(student int64) TunnelInfo {
SrvPubKey: srv_pubkey, SrvPubKey: srv_pubkey,
SrvPort: 42912, SrvPort: 42912,
CltIPv6: adlin.StudentIP(student), CltIPv6: adlin.StudentIP(student),
CltRange: 80, CltRange: adlin.StdNetmask,
SrvGW6: "2a01:e0a:2b:2252::1", SrvGW6: "2a01:e0a:2b:2252::1",
} }
} }
@ -121,11 +123,16 @@ func getWgTunnelInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Param
return return
} }
// 0 is considered default for suffix, apply default now
if token.SuffixIP <= 0 {
token.SuffixIP = 1
}
syncWgConf() syncWgConf()
tinfo := getTunnelInfo(token.IdStudent) tinfo := getTunnelInfo(token.IdStudent)
var student adlin.Student var student *adlin.Student
student, err = adlin.GetStudent(int(token.IdStudent)) student, err = adlin.GetStudent(int(token.IdStudent))
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
@ -138,11 +145,58 @@ PublicKey = %s
Endpoint = %s:%d Endpoint = %s:%d
AllowedIPs = ::/0 AllowedIPs = ::/0
PersistentKeepalive = 5 PersistentKeepalive = 5
# MyIPv6=%s1/%d # MyIPv6=%s%x/%d
# MyNetwork=%s/%d # MyNetwork=%s/%d
# GWIPv6=%s # GWIPv6=%s
# MyLogin=%s # MyLogin=%s
`, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6, student.Login))) `, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, token.SuffixIP, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6, student.Login)))
}
func updateWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token")))
if err != nil {
return nil, err
}
if token.IdStudent != student.Id {
return nil, fmt.Errorf("Unauthorized")
}
var newToken adlin.TunnelToken
if err := json.Unmarshal(body, &newToken); err != nil {
return nil, err
}
token.TokenText = newToken.TokenText
token.PubKey = newToken.PubKey
token.SuffixIP = newToken.SuffixIP
if _, err = token.Update(); err != nil {
return nil, err
}
syncWgConf()
return true, err
}
func deleteWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token")))
if err != nil {
return nil, err
}
if token.IdStudent != student.Id {
return nil, fmt.Errorf("Unauthorized")
}
if _, err = token.Delete(); err != nil {
return nil, err
}
syncWgConf()
return true, err
} }
func GenWGConfig(w io.Writer) error { func GenWGConfig(w io.Writer) error {

View file

@ -1,9 +1,9 @@
kernel: kernel:
image: linuxkit/kernel:4.19.104 image: linuxkit/kernel:4.19.121
cmdline: "console=tty0 console=ttyS0 root=/dev/sda1 root=/dev/sr0 adlin.format=/dev/sda quiet" cmdline: "console=tty0 console=ttyS0 root=/dev/sda1 root=/dev/sr0 adlin.format=/dev/sda quiet"
init: init:
- nemunaire/adlin-tuto2:41e341472a4a1b27dcf61c7d364f1f0a5f76fbe7 - nemunaire/adlin-tuto2:a68d5f224331628dc525edf383ec7429dfe001b0
files: files:
- path: etc/hostname - path: etc/hostname
@ -15,7 +15,7 @@ files:
- path: etc/resolv.conf - path: etc/resolv.conf
contents: | contents: |
nameserver 9.9.9.9 nameserver 9.9.9.10
nameserver 1.1.1.1 nameserver 1.1.1.1
uid: 0 uid: 0
gid: 0 gid: 0
@ -154,6 +154,14 @@ files:
/bin/ip -6 route del default /bin/ip -6 route del default
/bin/ip -6 route add default via $(sed 's/^.*GWIPv6=//p;d' etc/wireguard/adlin.conf) pref high /bin/ip -6 route add default via $(sed 's/^.*GWIPv6=//p;d' etc/wireguard/adlin.conf) pref high
# Download intermediate fixes
curl -s -f -H "X-ADLIN-time: $(stat -c %Y /boot)" https://adlin.nemunai.re/fix-vm2 | sh
# Retrieve ssh keys
mkdir -p root/.ssh/
[ -f root/.ssh/authorized_keys ] || /usr/sbin/chroot . /usr/bin/curl -s -f https://cri.epita.fr/$(sed 's/^.*MyLogin=//p;d' etc/wireguard/adlin.conf).keys > root/.ssh/authorized_keys
[ -f etc/ssh/ssh_host_rsa_key ] || /usr/sbin/chroot . ssh-keygen -A
# To the user # To the user
exec /usr/sbin/chroot . "${INITP}" exec /usr/sbin/chroot . "${INITP}"
uid: 0 uid: 0
@ -170,7 +178,7 @@ files:
- path: etc/shadow - path: etc/shadow
contents: | contents: |
root:$6$QNuPvO59Xk4UO3le$3P0V2ef6dHlKgO1FHsKcPPgOvL.YeCOPFqfIVTtpYn5eEn3xkgGYeM1RMCQ9l/eTc6rRc.l.WeRe1iJVznVGj/:18336:0:99999:7::: root:$6$dQXVLB.662ob0XJL$wRhh73Q.Z3mBRHhM0rSw96dE0bOFykfIXa2Z2ncu6WVSOpFLdv5J6Br9AHhalO4wwG3xgPqqhvCdEMdroR2r50:18336:0:99999:7:::
daemon:*:18316:0:99999:7::: daemon:*:18316:0:99999:7:::
bin:*:18316:0:99999:7::: bin:*:18316:0:99999:7:::
sys:*:18316:0:99999:7::: sys:*:18316:0:99999:7:::

View file

@ -20,7 +20,9 @@ Ansible est une solution de gestion de configuration. Basé sur
[YAML](http://www.yaml.org/spec/1.2/spec.html), sa principale particularité est [YAML](http://www.yaml.org/spec/1.2/spec.html), sa principale particularité est
de ne pas nécessité de daemon sur les machines qu'il va gérer : tout se fait de ne pas nécessité de daemon sur les machines qu'il va gérer : tout se fait
exclusivement via SSH, à partir de la machine d'un administrateur, possédant exclusivement via SSH, à partir de la machine d'un administrateur, possédant
Ansible. Ansible (ou bien d'un système de gestion de configuration tel qu'[Ansible
Tower](https://www.ansible.com/products/tower) ou
[AWX](https://github.com/ansible/awx)).
Son installation est très simple, car les dépendances sont minimes et l'outil Son installation est très simple, car les dépendances sont minimes et l'outil
n'a pas besoin de base de données pour fonctionner : tout va se faire à partir n'a pas besoin de base de données pour fonctionner : tout va se faire à partir
@ -48,7 +50,7 @@ système et des utilisateurs.
Un deuxième playbook est à rendre : `login-x-TP2/vitrine.yml`, celui-ci doit Un deuxième playbook est à rendre : `login-x-TP2/vitrine.yml`, celui-ci doit
permettre de déployer (en parallèle de tous les autres), une page vitrine permettre de déployer (en parallèle de tous les autres), une page vitrine
typique d'une entreprise (cf. la 4e question de cours ;)). Cette page doit être typique d'une entreprise (cf. la 4e question de cours ;)). Cette page doit être
accessible depuis votre domaine <https://login-x.adlin2021.p0m.fr/>. accessible depuis votre domaine <https://login-x.adlin2022.p0m.fr/>.
Mon première commande Mon première commande
@ -85,7 +87,7 @@ Lancez ensuite la commande suivante :
<div lang="en-US"> <div lang="en-US">
``` ```
42sh$ ansible --inventory-file hosts all --module-name ping --user root --ask-pass 42sh$ ansible --inventory-file hosts all --module-name ping --user root
192.168.0.106 | SUCCESS => { 192.168.0.106 | SUCCESS => {
"changed": false, "changed": false,
"ping": "pong" "ping": "pong"
@ -97,6 +99,10 @@ Vous devriez avoir un retour similaire à celui-ci, indiquant simplement que la
connexion a bien été effectuée et que le nécessaire est bien installé sur la connexion a bien été effectuée et que le nécessaire est bien installé sur la
machine distance. machine distance.
Si votre clef SSH n'a pas été récupérée depuis le CRI, vous pouvez rajouter
l'option `--ask-pass`, afin de pouvoir indiquer le mot de passe de connexion au
compte, sinon vous obtiendrez une erreur plutôt incrompréhensible.
### Confort ### Confort
@ -281,7 +287,7 @@ pour davantage de détails et d'exemples.
La configuration de votre serveur SSH laisse à désirer. Corriger les problèmes La configuration de votre serveur SSH laisse à désirer. Corriger les problèmes
énoncés par ces deux articles : énoncés par ces deux articles :
- <https://ringyt.wordpress.com/2017/05/23/default-hardened-ssh-server-config/> ; - <https://linuxhandbook.com/ssh-hardening-tips/> ;
- <https://stribika.github.io/2015/01/04/secure-secure-shell.html>. - <https://stribika.github.io/2015/01/04/secure-secure-shell.html>.
Mettez en place un *handler* pour relancer votre serveur SSH en cas de Mettez en place un *handler* pour relancer votre serveur SSH en cas de

View file

@ -9,7 +9,7 @@ gestionnaire de configuration.
Après cet échauffement, vous devriez être prêt à créer un *playbook* Après cet échauffement, vous devriez être prêt à créer un *playbook*
dédié à l'installation d'un serveur [Matrix](https://matrix.org/) : dédié à l'installation d'un serveur [Matrix](https://matrix.org/) :
![Riot](riot.png "Riot : un des clients utilisable pour joindre son serveur Matrix") ![Element](riot.png "Element : un des clients utilisable pour joindre son serveur Matrix")
Vous connaissez et utilisez sans doute Slack, un service de messagerie Vous connaissez et utilisez sans doute Slack, un service de messagerie
@ -34,7 +34,7 @@ dédié lorsque c'est possible (pas de `root` !), droits d'accès et
permissions des répertoires, etc. permissions des répertoires, etc.
Profitez des [modules de base de Profitez des [modules de base de
données](http://docs.ansible.com/ansible/latest/list_of_database_modules.html) données](https://docs.ansible.com/ansible/2.9/modules/list_of_database_modules.html)
pour l'initialiser correctement. Et bien entendu de l'ensemble des pour l'initialiser correctement. Et bien entendu de l'ensemble des
modules décrits dans la documentation standard ! modules décrits dans la documentation standard !
@ -58,7 +58,7 @@ de données. Le *dump* obtenu est à placer dans `/var/backups/`.
## Client de test ## Client de test
Vous n'êtes pas tenu d'installer un client. Pour vos tests, vous pouvez Vous n'êtes pas tenu d'installer un client. Pour vos tests, vous pouvez
utiliser <https://riot.im/app/>, en changeant l'adresse du serveur Matrix pour utiliser <https://app.element.io/>, en changeant l'adresse du serveur Matrix pour
votre sous-domaine dédié à Matrix (normalement votre sous-domaine dédié à Matrix (normalement
<https://matrix.login-x.srs.p0m.fr/>). (Conservez le serveur d'identité à <https://matrix.login-x.srs.p0m.fr/>). (Conservez le serveur d'identité à
<https://vector.im>). <https://vector.im>).
@ -68,7 +68,7 @@ votre sous-domaine dédié à Matrix (normalement
## Validation ## Validation
Pour valider l'installation de votre serveur, rejoignez le canal Pour valider l'installation de votre serveur, rejoignez le canal
`#adlin:nemunai.re` et envoyez un message « Ping ! » à `@nemubot:nemunai.re` `#adlin:nemunai.re` et envoyez un message « Ping ! » pour signaler votre
qui s'occupera de valider ce pallier. présence.
Vous devriez également pouvoir tester entre-vous. Vous devriez également pouvoir tester entre-vous.

View file

@ -42,9 +42,10 @@ Tunnel IPv6
Au premier lancement de votre VM, la machine vous demandera d'indiquer un jeton Au premier lancement de votre VM, la machine vous demandera d'indiquer un jeton
afin de mettre en place le tunnel IPv6. afin de mettre en place le tunnel IPv6.
Afin d'en obtenir un, rendez-vous sur la page Tunnels et créez un nouveau Afin d'en obtenir un, rendez-vous sur la [page
tunnel. Un jeton de 10 caractères s'affichera alors, c'est celui que vous Tunnels](https://adlin.nemunai.re/maatma/tunnels) et créez un nouveau
devrez recopier dans le terminal (attention à la casse !). tunnel. Un jeton de 10 caractères s'affichera alors, c'est celui que
vous devrez recopier dans le terminal (attention à la casse !).
### Test du tunnel ### Test du tunnel

View file

@ -19,3 +19,136 @@ Voici les grandes étapes :
* tester avec `dig @9.9.9.9 ANY login-x.srs.p0m.fr` que votre serveur est bien joignable. * tester avec `dig @9.9.9.9 ANY login-x.srs.p0m.fr` que votre serveur est bien joignable.
Un ouvrage de référence, qui répondra à l'intégralité de vos questions et manières d'utiliser votre serveur DNS se trouve à <http://www.zytrax.com/books/dns/>. Un ouvrage de référence, qui répondra à l'intégralité de vos questions et manières d'utiliser votre serveur DNS se trouve à <http://www.zytrax.com/books/dns/>.
## Coller aux spécifications
Pourquoi une telle complexité apparente pour déléguer une zone DNS ?
Vous êtes sur le point d'obtenir le contrôle total d'un domaine :
`login-x.srs.p0m.fr.`. Cela signifie que vous allez pouvoir décider,
depuis votre propre serveur de noms (respectueux des
[standards](https://www.ietf.org/rfc/rfc1034.txt)), comment vous allez
répondre aux serveurs résolveurs des utilisateurs.
Le protocole DNS étant décentralisé, mais basé sur une arborescence
unique, il est nécessaire que les serveurs faisant autorité sur une
zone (`fr.`, `srs.p0m.fr.`, ...) fournissent toutes les informations
nécessaire pour que cette délégation fonctionne.
À cet instant, vous connaissez l'adresse IPv6 routable sur Internet de votre
serveur de noms (comme vous n'avez aujourd'hui qu'une seule machine, c'est
l'adresse de votre tunnel). Il va donc falloir indiquer à Internet que c'est
cette IP qu'il faut contacter, si l'on veut interroger le serveur faisant
autorité.
Concrètement, vous allez devoir vous attribuer un sous-domaine pour votre
serveur de noms, car c'est grâce à un enregistrement `NS` figurant dans la zone
parente (pour vous, la zone parente c'est `srs.p0m.fr`) que la délégation va se
faire.
Dans le cas basique, on va utiliser les serveurs de quelqu'un d'autre (d'un
hébergement spécialisé par exemple, ou celui de son bureau d'enregistrement
s'ils proposent ce service). Auquel cas, on fera figurer ce genre
d'informations :
```
42sh$ dig @e.ext.nic.fr. NS epita.fr.
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 17590
;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 0
;; QUESTION SECTION:
;; epita.fr. IN NS
;; AUTHORITY SECTION:
epita.fr. 172800 IN NS tooty.ionis-it.com.
epita.fr. 172800 IN NS kazooie.ionis-it.com.
epita.fr. 172800 IN NS banjo.ionis-it.com.
;; Received 100 B
;; Time 2042-12-04 13:42:23 CET
;; From 2a00:d78:0:102:193:176:144:22@53(UDP) in 13.9 ms
```
La commande est envoyée spécifiquement aux serveurs de l'AFNIC, faisant
autorité pour la zone `fr.`. Les serveurs de l'AFNIC nous indiquent en retour,
que `epita.fr.` est déléguée à Ionis, et qu'elle dispose de 3 serveurs faisant
autorité.
Mais dans votre cas, vous hébergez vous-même votre propre serveur au sein de
votre zone, vous n'avez pas d'autre domaine à votre disposition. C'est
également le cas de Wikipédia :
```
42sh$ dig @d0.org.afilias-nst.org. NS wikipedia.org.
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 47396
;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 3
;; QUESTION SECTION:
;; wikipedia.org. IN NS
;; AUTHORITY SECTION:
wikipedia.org. 86400 IN NS ns0.wikimedia.org.
wikipedia.org. 86400 IN NS ns2.wikimedia.org.
wikipedia.org. 86400 IN NS ns1.wikimedia.org.
[...]
```
On voit ici que pour résoudre les sous-domaines de `wikipedia.org.`, il faut
demander à `nsX.wikimedia.org.`. Mais comment obtenir alors, l'adresse de ces
serveurs de noms, puisque l'on ne sait pas où les contacter ...!
C'est là que les *GLUE records* entrent en jeu !
J'ai volontairement tronqué la sortie de la commande précédente, en entier, c'est :
```
42sh$ dig @d0.org.afilias-nst.org. NS wikipedia.org.
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 47396
;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 3
;; QUESTION SECTION:
;; wikipedia.org. IN NS
;; AUTHORITY SECTION:
wikipedia.org. 86400 IN NS ns0.wikimedia.org.
wikipedia.org. 86400 IN NS ns2.wikimedia.org.
wikipedia.org. 86400 IN NS ns1.wikimedia.org.
;; ADDITIONAL SECTION:
ns0.wikimedia.org. 86400 IN A 208.80.154.238
ns1.wikimedia.org. 86400 IN A 208.80.153.231
ns2.wikimedia.org. 86400 IN A 91.198.174.239
;; Received 143 B
;; Time 2042-12-04 13:42:23 CET
;; From 2001:500:f::1@53(UDP) in 15.2 ms
```
Afin d'éviter de tourner en rond sans jamais avoir réponse à notre question, en
même temps que de nous répondre sur qui sont les serveurs faisant autorité pour
la zone `wikipedia.org.`, les serveurs gérant la zone `org.` indiquent
également à quelle adresse ont peut les contacter.
Ce sont les administrateurs de la zone `wikipedia.org.` qui ont indiqué à
`org.` quels étaient leurs serveurs de noms. Et ils ont également donné des
*GLUE records*, pour permettre à la magie d'opérer.
À vous maintenant de créer votre zone, en envoyant sur Maatma, le nom de
domaine votre serveur de noms, ainsi que le *GLUE record* qui lui correspond.
## DNSSEC (bonus)
En bonus, vous devriez sécuriser les réponses envoyées par votre serveur DNS.
Pour ce faire, et toujours parce que l'on se trouve dans le cadre d'une
structure arborescente, les clefs publiques permettant de valider les
enregistrements d'un domaine en particulier, sont publiées dans la zone
parente. C'est pourquoi, vous disposez sur Maatma, d'une interface vous
permettant d'indiquer vos clefs publiques.
De nombreux articles sont disponibles sur Internet pour vous permettre de
configurer DNSSEC en fonction du serveur autoritaire que vous aurez choisi. À
vous de jouer !

View file

@ -33,7 +33,7 @@ L'image d'installation
---------------------- ----------------------
Vous pouvez télécharger l'ISO du TP depuis Vous pouvez télécharger l'ISO du TP depuis
<https://adlin.nemunai.re/ressources/tuto2.iso>. <https://adlin.nemunai.re/resources/tuto2.iso>.
Cette image contient un système Debian minimaliste, en partie préinstallé afin Cette image contient un système Debian minimaliste, en partie préinstallé afin
de vous permettre de commencer à travailler sans plus attendre ! de vous permettre de commencer à travailler sans plus attendre !
@ -261,8 +261,8 @@ machine, afin qu'elle ne teste que le lecteur de CD virtuel.
Connexion Connexion
--------- ---------
La machine ne se connecte pas au réseau toute seule, vous allez devoir l'aider Si la machine ne se connecte pas au réseau toute seule, vous allez devoir
en reproduisant les étapes que nous avons apprises au TP précédent. l'aider en reproduisant les étapes que nous avons apprises au TP précédent.
### Requête DHCP ### Requête DHCP
@ -295,7 +295,7 @@ pouvoir émettre un paquet sur le réseau.
``` ```
42sh$ ip link 42sh$ ip link
1: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000 1: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
^^^^^^^^^^ ^^^^^^^^^^
``` ```
</div> </div>

View file

@ -3,18 +3,18 @@ title: Administration Linux avancée -- TP n^o^ 2
subtitle: "Maatma : l'hébergeur DIY" subtitle: "Maatma : l'hébergeur DIY"
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
institute: EPITA institute: EPITA
date: Lundi 16 mars 2020 date: Jeudi 4 mars 2021
abstract: | abstract: |
Durant ce deuxième TP, nous allons apprendre à déployer des services sur un Durant ce deuxième TP, nous allons apprendre à déployer des services sur un
serveur, de manière industrielle ! serveur, de manière industrielle !
\vspace{1em} \vspace{1em}
La partie 4 de ce TP est un projet à rendre à <adlin@nemunai.re> au plus tard La partie 5 de ce TP est un projet à rendre à <adlin@nemunai.re> au plus tard
le **lundi 30 mars 2020 à 00 h 42 du matin**. Consultez la dernière le **jeudi 18 mars 2021 à 12 h 42**. Consultez la dernière
section de chaque partie pour plus d'information sur les éléments à section de chaque partie pour plus d'information sur les éléments à
rendre. Et n'oubliez pas de répondre aux [questions de rendre. Et n'oubliez pas de répondre aux [questions de
cours](https://adlin.nemunai.re/quiz/2). cours](https://adlin.nemunai.re/quiz/9).
En tant que personnes sensibilisées à la sécurité des échanges électroniques, En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à

View file

@ -20,7 +20,7 @@ reporter dans un fichiers au chapitre suivant !
Ma première vitrine Ma première vitrine
------------------- -------------------
Sur le domaine `login_x.adlin2021.p0m.fr`, déployez une vitrine d'entreprise Sur le domaine `login-x.adlin2022.p0m.fr`, déployez une vitrine d'entreprise
basique (pas besoin d'un Wordpress, un simple lot de pages HTML fera l'affaire). basique (pas besoin d'un Wordpress, un simple lot de pages HTML fera l'affaire).
Vous aurez pour cela besoin d'un serveur web, dont le choix est laissé à votre Vous aurez pour cela besoin d'un serveur web, dont le choix est laissé à votre
@ -29,11 +29,13 @@ discrétion.
Vous pouvez utiliser les services de [Let's Encrypt](https://letsencrypt.org/) Vous pouvez utiliser les services de [Let's Encrypt](https://letsencrypt.org/)
pour obtenir un certificat TLS. Compte tenu des limitations imposées, vous ne pour obtenir un certificat TLS. Compte tenu des limitations imposées, vous ne
pourrez pas tous en créer un aujourd'hui, mais n'hésitez pas à retenter un peu pourrez pas tous en créer un aujourd'hui, mais n'hésitez pas à retenter un peu
plus tard dans la semaine. plus tard dans la semaine. Vous pouvez également obtenir vos certificats depuis
de nombreux autres services gratuits similaire :
[ZeroSSL](https://zerossl.com/), [buypass](https://www.buypass.com/), ...
*D'ailleurs, si vous disposez de votre propre nom de domaine et que vous D'ailleurs, si vous disposez de votre propre nom de domaine et que vous
souhaitez l'utiliser pour ce TP, n'hésitez pas à me solliciter pour que je souhaitez l'utiliser pour ce TP, vous pouvez suivre les instructions dans
mette en place les redirections adéquates.* l'interface de Maatma pour pouvoir l'utiliser.
Une fois votre serveur web configuré et votre vitrine installée, accédez à Une fois votre serveur web configuré et votre vitrine installée, accédez à

View file

@ -7,7 +7,7 @@ Accéder à la machine virtuelle
------------------------------ ------------------------------
Une fois la machine virtuelle démarrée, vous pouvez vous y connecter en `root` Une fois la machine virtuelle démarrée, vous pouvez vous y connecter en `root`
avec le mot de passe `adlin2021`. avec le mot de passe `adlin2022`.
Vous pouvez également démarrer en mode *single user*, mais comme votre disque Vous pouvez également démarrer en mode *single user*, mais comme votre disque
n'est sans doute pas encore utilisable à ce stade, vous ne pourrez pas changer n'est sans doute pas encore utilisable à ce stade, vous ne pourrez pas changer
@ -30,6 +30,13 @@ local (sans passer par un NAT), n'oubliez pas de modifier le mot de passe
`root` pour éviter que n'importe qui sur le réseau local (et ayant accès à ce `root` pour éviter que n'importe qui sur le réseau local (et ayant accès à ce
TP), ne rentre sur votre machine. TP), ne rentre sur votre machine.
Afin de vous faciliter la configuration de la machine par la suite, vos clefs
SSH publiques, [déclarées au
CRI](https://cri.epita.fr/users/nemunaire/ssh-keys/), sont automatiquement
ajoutées à l'utilisateur `root`. Rendez-vous dans l'interface du CRI pour les
mettre à jour si besoin. Notez qu'elles ne sont retéléchargées que si le
fichier `authorized_keys` n'existe pas.
### Création d'un utilisateur ### Création d'un utilisateur