Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add internal routes for ssh hook comands #1471

Merged
merged 7 commits into from Apr 19, 2017

Conversation

lunny
Copy link
Member

@lunny lunny commented Apr 8, 2017

This is a first part for convert database operation to internal http operation for SSH hook commands. This will reduce the potential concurrent problem when use sqlite or tidb.

@lunny lunny added the type/refactoring Existing code has been cleaned up. There should be no new functionality. label Apr 8, 2017
@lunny lunny added this to the 1.2.0 milestone Apr 8, 2017
@lunny
Copy link
Member Author

lunny commented Apr 8, 2017

related to #1023

@tboerger tboerger added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Apr 8, 2017
@strk
Copy link
Member

strk commented Apr 8, 2017

This sounds overcomplex to me. Why don't both SSH and WEB access the database via an internal API instead ?

@lunny
Copy link
Member Author

lunny commented Apr 8, 2017

for sqlite and tidb, web command will open database and every push or pull via SSH will open database. It will result in concurrent problem and will spent more time when push or pull via SSH than open only one.

@strk
Copy link
Member

strk commented Apr 8, 2017

Yes I understand the goal of the code, but I'm not sure about the implementation. Using the web requires authentication to guarantee the call comes from the inside, is this what the JWT is used for ?

Also, is it currently guarantee that there's a single database connection always ? No pooling ?

@typeless
Copy link
Contributor

typeless commented Apr 8, 2017

As far as I know, SQLite3 can handle concurrent operations in its own right (if it's not buggy). And for http requests, they can be handled concurrently as well. So I am afraid centralizing the responsibilities to the http router couldn't help too much.

I think the most effectives, and also the most challenging at the same time, is to incrementally eliminate the global variables/singletons.

Btw, the Go compiler developers are doing the similar things (removing global variables) for the compiler recently for concurrent compiling. I think we can not avoid it either. The problem will continue to haunt us if the code still sits on top of the globals.

Copy link
Member

@bkcsoft bkcsoft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT-tokens with expiration makes no sense here. 5 minutes after gitea is started all SSH-access will fail (or at least error) since the token isn't being updated. IMO we should just set one token and keep stick with that (like security.SECRET_KEY). Further security improvements involves having /internal only accessable to localhost (since gitea doesn't support multi-host setups 😉

@@ -0,0 +1,51 @@
package private
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it called private when then endpoint is /internal ? Pick one and stay with it 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because Go don't allow a package named internal

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh right, internal is "special" 😒

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using /api/private then ? It's good to be consistent :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And private.go, for that same reason...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@strk /api/private is inaccurate than /api/internal I think.


now := time.Now()
InternalToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"exp": now.Add(5 * time.Minute).Unix(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either use a token with expiration, OR save it in the ini-file, not both. It makes no sense to persist ephemeral values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have saved it to ini file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case, skip the expiration

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
log.Fatal(4, "Failed to create '%s': %v", CustomConf, err)
}
if err := cfgSave.SaveTo(CustomConf); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either use a token with expiration, OR save it in the ini-file, not both. It makes no sense to persist ephemeral values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a reason to keep that token in the Ini file either, just have it randomly generated upon start and let it die/regenerate on restart

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we have to share the token between gitea web and gitea serv and gitea hook. And gitea web should be run firstly, then it will save token to ini if there is no token. After that, all other commands could read it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@strk serv still has to get it from somewhere. This still makes sense :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, thanks

// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package private
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, internal or private doesn't matter to me, but the package name should reflect the endpoint name 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I will change all internal to private.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM actually, /private makes less sense than /internal. Just add a comment above package private explaining why it's called private instead of internal 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can just call it /ssh rather than /internal/ssh if there is no conflict.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized there already is modules/ssh. Does it make any sense to combine the two?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@typeless it's not only for ssh, it's also for hooks commands. The hooks commands will be fired by both SSH and HTTPS. So I think /ssh is not suitable. modules/ssh is for builtin SSH server, it's not related with this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkcsoft done.


// RegisterRoutes registers all internal APIs routes to web application.
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
func RegisterRoutes(m *macaron.Macaron) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need to start doing this in more places ❤️

@lunny
Copy link
Member Author

lunny commented Apr 9, 2017

@typeless Maybe sqlite has share mode, but tidb don't support. So currently tidb in fact cannot work via SSH.
Another obviously benefit of this PR is it will reduce SSH command execute time. When a push or pull to gitea, serv will be invoked and all gitea hooks will be invoked. Every command will read the ini file and open sqlite or tidb or create connection pool on mysql/postgres/mssql. So totally there are FOUR times duplicated operations. After we move all this to an internal web service. It will no need. And the web service should have started when you run gitea web.

@tboerger
Copy link
Member

tboerger commented Apr 9, 2017

And the global variables are a totally different topic that got nothing to do with that...

@typeless
Copy link
Contributor

@lunny Eliminating duplicate operations is all good, I agree. But I still cannot see how routing requests via a single entry can prevent data races. Aren't the requests still handled concurrently without explicit serialization (i.e. being protected by mutexes)? To put it differently, is a program automatically race-free without adding any mutex when all the concurrent routines have a common entry? It's not.

Maybe you have a bigger picture in mind beyond the data race issue. I am not against what the PR does, just questioning about it being a fix for data race. The purpose of reducing duplicates can justify it already.

}

resp.Body.Close()
if resp.StatusCode/100 != 2 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid if the StatusCode is in 200...299 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, got it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lunny Please add a comment above as it's not clear why this check is here :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@strk
Copy link
Member

strk commented Apr 11, 2017 via email

@lunny
Copy link
Member Author

lunny commented Apr 12, 2017

@strk good idea, but it's difficult to control the permission if we put it in /api/internal because /api has different permission requirement from /internal.

@typeless
Copy link
Contributor

@strk I see. I used to imagine the internal routes would not be visible from the external. All urls passed from the external client were translated rather than mixing the two namespaces. I don't know whether doing so would be too heavy-weight or not. Every added abstraction solving a problem also comes with its cost. I would avoid too-clever techniques given my knowledge in this area (webdev) is not comprehensive to say the least.

@bkcsoft
Copy link
Member

bkcsoft commented Apr 12, 2017

because /api has different permission requirement from /internal.

@lunny This is IMO a bug that should be fixed, not something to work around. I agree with @strk that it should be /api/internal. Putting it in /api makes for less conflicts so that makes more sense. (As is now, you can no longer create internal organization/user which is a breaking change 😒 )

cmd/web.go Outdated
@@ -659,6 +660,11 @@ func runWeb(ctx *cli.Context) error {
apiv1.RegisterRoutes(m)
}, ignSignIn)

m.Group("/internal", func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should go in /api/internal

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@bkcsoft
Copy link
Member

bkcsoft commented Apr 18, 2017

I'm fine with this workaround for the time being. LGTM

@tboerger tboerger added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Apr 18, 2017
@lunny lunny added the type/bug label Apr 19, 2017
return err
}

resp.Body.Close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using defer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@lunny
Copy link
Member Author

lunny commented Apr 19, 2017

will fix #1479

@appleboy
Copy link
Member

LGTM

@tboerger tboerger added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Apr 19, 2017
@lunny lunny merged commit 2eeae84 into go-gitea:master Apr 19, 2017
@lunny lunny deleted the lunny/internal_routes branch April 19, 2017 03:45
@go-gitea go-gitea locked and limited conversation to collaborators Nov 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/bug type/refactoring Existing code has been cleaned up. There should be no new functionality.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants