1package credentials
2
3import (
4	"bufio"
5	"bytes"
6	"fmt"
7	"io"
8	"os"
9	"os/exec"
10	"os/user"
11	"sync"
12	"testing"
13
14	"github.com/jcmturner/gokrb5/v8/iana/nametype"
15	"github.com/jcmturner/gokrb5/v8/test"
16	"github.com/jcmturner/gokrb5/v8/test/testdata"
17	"github.com/jcmturner/gokrb5/v8/types"
18	"github.com/stretchr/testify/assert"
19)
20
21const (
22	kinitCmd = "kinit"
23	kvnoCmd  = "kvno"
24	klistCmd = "klist"
25	spn      = "HTTP/host.test.gokrb5"
26)
27
28type output struct {
29	buf   *bytes.Buffer
30	lines []string
31	*sync.Mutex
32}
33
34func newOutput() *output {
35	return &output{
36		buf:   &bytes.Buffer{},
37		lines: []string{},
38		Mutex: &sync.Mutex{},
39	}
40}
41
42func (rw *output) Write(p []byte) (int, error) {
43	rw.Lock()
44	defer rw.Unlock()
45	return rw.buf.Write(p)
46}
47
48func (rw *output) Lines() []string {
49	rw.Lock()
50	defer rw.Unlock()
51	s := bufio.NewScanner(rw.buf)
52	for s.Scan() {
53		rw.lines = append(rw.lines, s.Text())
54	}
55	return rw.lines
56}
57
58func login() error {
59	file, err := os.Create("/etc/krb5.conf")
60	if err != nil {
61		return fmt.Errorf("cannot open krb5.conf: %v", err)
62	}
63	defer file.Close()
64	fmt.Fprintf(file, testdata.TEST_KRB5CONF)
65
66	cmd := exec.Command(kinitCmd, "testuser1@TEST.GOKRB5")
67
68	stdinR, stdinW := io.Pipe()
69	stderrR, stderrW := io.Pipe()
70	cmd.Stdin = stdinR
71	cmd.Stderr = stderrW
72
73	err = cmd.Start()
74	if err != nil {
75		return fmt.Errorf("could not start %s command: %v", kinitCmd, err)
76	}
77
78	go func() {
79		io.WriteString(stdinW, "passwordvalue")
80		stdinW.Close()
81	}()
82	errBuf := new(bytes.Buffer)
83	go func() {
84		io.Copy(errBuf, stderrR)
85		stderrR.Close()
86	}()
87
88	err = cmd.Wait()
89	if err != nil {
90		return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes()))
91	}
92	return nil
93}
94
95func getServiceTkt() error {
96	cmd := exec.Command(kvnoCmd, spn)
97	err := cmd.Start()
98	if err != nil {
99		return fmt.Errorf("could not start %s command: %v", kvnoCmd, err)
100	}
101	err = cmd.Wait()
102	if err != nil {
103		return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err)
104	}
105	return nil
106}
107
108func klist() ([]string, error) {
109	cmd := exec.Command(klistCmd, "-Aef")
110
111	stdout := newOutput()
112	cmd.Stdout = stdout
113
114	err := cmd.Start()
115	if err != nil {
116		return []string{}, fmt.Errorf("could not start %s command: %v", klistCmd, err)
117	}
118
119	err = cmd.Wait()
120	if err != nil {
121		return []string{}, fmt.Errorf("%s did not run successfully: %v", klistCmd, err)
122	}
123
124	return stdout.Lines(), nil
125}
126
127func loadCCache() (*CCache, error) {
128	usr, _ := user.Current()
129	cpath := "/tmp/krb5cc_" + usr.Uid
130	return LoadCCache(cpath)
131}
132
133func TestLoadCCache(t *testing.T) {
134	test.Privileged(t)
135
136	err := login()
137	if err != nil {
138		t.Fatalf("error logging in with kinit: %v", err)
139	}
140	c, err := loadCCache()
141	if err != nil {
142		t.Errorf("error loading CCache: %v", err)
143	}
144	pn := c.GetClientPrincipalName()
145	assert.Equal(t, "testuser1", pn.PrincipalNameString(), "principal not as expected")
146	assert.Equal(t, "TEST.GOKRB5", c.GetClientRealm(), "realm not as expected")
147}
148
149func TestCCacheEntries(t *testing.T) {
150	test.Privileged(t)
151
152	err := login()
153	if err != nil {
154		t.Fatalf("error logging in with kinit: %v", err)
155	}
156	err = getServiceTkt()
157	if err != nil {
158		t.Fatalf("error getting service ticket: %v", err)
159	}
160	clist, _ := klist()
161	t.Log("OS Creds Cache contents:")
162	for _, l := range clist {
163		t.Log(l)
164	}
165	c, err := loadCCache()
166	if err != nil {
167		t.Errorf("error loading CCache: %v", err)
168	}
169	creds := c.GetEntries()
170	var found bool
171	n := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn)
172	for _, cred := range creds {
173		if cred.Server.PrincipalName.Equal(n) {
174			found = true
175			break
176		}
177	}
178	if !found {
179		t.Errorf("Entry for %s not found in CCache", spn)
180	}
181}
182